diff --git a/BetterShop_Activepro18.csv b/BetterShop_Activepro18.csv new file mode 100644 index 0000000..d902396 --- /dev/null +++ b/BetterShop_Activepro18.csv @@ -0,0 +1,43 @@ +id,subdata,buyprice,sellprice,name +1,0,10,5,stone +2,0,10,5,grass +3,0,10,5,dirt +4,0,10,5,cobblestone +5,0,15,5,wood +6,0,7,3,sapling +7,0,12500,12,bedrock +12,0,13,3,sand +13,0,12,4,gravel +17,0,15,6,log +19,0,1000,100,sponge +20,0,20,10,glass +23,0,30,10,dispenser +24,0,20,10,sandstone +25,0,60,10,noteblock +45,0,30,10,brickblock +46,0,30,15,tnt +47,0,50,20,bookshelf +50,0,5,2,torch +54,0,25,10,chest +65,0,20,10,ladder +66,0,20,10,minecarttrack +84,0,60,10,jukebox +86,0,15,5,pumpkin +87,0,20,10,netherack +88,0,40,15,soulsand +89,0,50,15,glowstone +91,0,10,5,jackolantern +260,0,20,16,apple +262,0,1.0,0.8,arrow +263,0,20,10,coal +264,0,300,70,diamond +265,0,70,20,ironingot +266,0,90,30,goldingot +289,0,25,5,gunpowder +297,0,10,5,bread +318,0,12,6,flint +320,0,15,5,grilledpork +322,0,300,,goldenapple +331,0,30,10,redstone +338,0,25,10,sugarcane +351,15,15,5,bonemeal diff --git a/BetterShop_Fob_Upset.csv b/BetterShop_Fob_Upset.csv new file mode 100644 index 0000000..6f2391d --- /dev/null +++ b/BetterShop_Fob_Upset.csv @@ -0,0 +1,157 @@ +id,subdata,buyprice,sellprice,name +1,,5,2.5,Stone +3,,1,0.25,Dirt +4,,1,0.25,Cobblestone +5,,2.5,1,Plank +6,,3,1.5,Sapling +12,,1.75,0.75,Sand +13,,3.5,1.75,Gravel +14,,45,35,Gold Ore +15,,35,25,Iron Ore +17,,8,4,Log +18,,100,25,Leaves +20,,20,7.5,Glass +22,,180,90,Lapis Lazuli Block +23,,60,31.75,Dispenser +24,,10,3,Sandstone +25,,40,28,Note Block +27,,365,182.5,Powered rail +28,,52,26,Detector rail +29,,288,144,Sticky Piston +30,,50,25,Web +33,,38,19,Piston +35,,3,1,Wool +37,,10,4,Yellow Flower +38,,10,4,Red Rose +39,,10,1,Brown Mushroom +40,,10,1,Red Mushroom +41,,495,405,Gold Block +42,,405,315,Iron Block +44,,15,3,Stone Slab +44,1,21,9,Sandstone Slab +44,2,7.5,3,Wooden Slab +44,3,3,0.75,Cobble Slab +45,,360,80,Brick +47,,51,24,Bookcase +48,,50,25,Mossy Cobblestone +49,,1000,500,Obsidian +50,,7,4,Torch +53,,4,1.5,Wooden Stairs +54,,20,9,Chest +58,,8,4,Workbench +61,,4,2,Furnace +65,,4.5,1.75,Ladder +66,,16,8,Minecart Tracks +67,,1.5,0.25,Cobblestone Stairs +69,,2,1,Lever +70,,10,5,Stone Pressure Plate +72,,5,2,Wooden Pressure Plate +76,,31,15,Redstone Torch +77,,10,5,Stone Button +81,,10,5,Cactus +84,,78,36,Jukebox +85,,6,3,Fence +86,,10,5,Pumpkin +87,,5,1,NetherRack +88,,6,2,Slow Sand +89,,60,30,Glowstone Block +91,,17,8.5,Jack-O-Lantern +96,,8,5.5,Trapdoor +256,,47,36,Iron Spade +257,,136,106,Iron Pickaxe +258,,136,106,Iron Axe +259,,50,40,Flint and Steel +260,,2,0.5,Red Apple +261,,35,15,Bow +262,,0.5,0.2,Arrow +263,,25,15,Coal +265,,45,35,Iron Ingot +266,,55,45,Gold Ingot +267,,91,71,Iron Sword +272,,3,1.5,Stone Sword +273,,2,1,Stone Spade +274,,4,2,Stone Pickaxe +275,,4,2,Stone Axe +280,,1,0.5,Stick +281,,7.5,3,Bowl +282,,10,5,Mushroom Soup +283,,57,28.5,Gold Sword +284,,56,28,Gold Spade +285,,58,29,Gold Pickaxe +286,,58,29,Gold Axe +287,,10,2.5,String +288,,3,1.5,Feather +289,,40,25,Gunpowder +291,,2,1,Stone Hoe +292,,91,70.5,Iron Hoe +294,,91,70.5,Gold Hoe +295,,3,0.5,Seeds +296,,5,3,Wheat +297,,15,5,Bread +298,,15,2.5,Leather Helmet +299,,24,4,Leather Chestplate +300,,21,3.5,Leather Pants +301,,12,2,Leather Boots +306,,225,175,Iron Helmet +307,,360,280,Iron Chestplate +308,,315,245,Iron Pants +309,,180,140,Iron Boots +314,,275,225,Gold Helmet +315,,440,360,Gold Chestplate +316,,385,315,Gold Pants +317,,220,180,Gold Boots +318,,4,2,Flint +320,,20,10,Grilled Pork +321,,11,5.5,Paintings +322,,3963,1981.5,Golden Apple +323,,13,7,Sign +324,,6,3,Wooden Door +325,,135,105,Bucket +326,,150,120,Water Bucket +328,,225,112.5,Mine Cart +329,,10,5,Saddle +330,,270,135,Iron Door +331,,30,20,Redstone +332,,1,0.25,Snowball +333,,10,5.5,Boat +334,,3,0.5,Leather +335,,150,120,Milk Bucket +336,,90,80,Clay Brick +337,,20,10,Clay Balls +338,,4,2,Reed +339,,4,2,Paper +340,,12,6,Book +341,,250,125,Slime Ball +344,,30,15,Egg +345,,210,105,Compass +346,,25,12.5,Fishing Rod +347,,250,125,Watch +348,,15,7.5,Glowstone Dust +349,,30,15,Raw Fish +350,,35,17.5,Cooked Fish +351,,6,3,Ink Sack +351,1,5,2,Rose red +351,2,11,6,Cactus Green +351,3,20,15,Cocoa Beans +351,4,20,10,Lapis Lazuli +351,5,25,12,Purple Dye +351,6,31,16,Cyan Dye +351,7,46,23,Light Gray Dye +351,8,26,13,Gray Dye +351,9,25,12,Pink Dye +351,10,31,16,Lime Dye +351,11,5,2,Dandelion Yellow +351,12,40,20,Light Blue Dye +351,13,50,24,Magenta Dye +351,14,10,4,Orange Dye +351,15,7,3.5,Bone Meal +352,,20,10,Bone +353,,4,2,Sugar +354,,353,176.5,Cake +355,,20,10,Bed +356,,107,53.5,Redstone Repeater +357,,3.75,1.8,Cookie +358,,242,121,Map +359,,90,70,Shears +2256,,2500,1500,Gold Record +2257,,2500,1500,Green Record \ No newline at end of file diff --git a/BetterShop_HammerCraftMC.csv b/BetterShop_HammerCraftMC.csv new file mode 100644 index 0000000..197d38d --- /dev/null +++ b/BetterShop_HammerCraftMC.csv @@ -0,0 +1,189 @@ +id,subdata,buyprice,sellprice,name +1,0,0.2,0.1,Stone +3,0,0.05,0.01,Dirt +4,0,0.1,0.05,Cobblestone +5,0,0.3,0.2,Wood +6,0,0.2,0.1,Sapling +12,0,0.3,0.2,Sand +13,0,0.3,0.2,Gravel +17,0,1,0.8,Log +20,0,0.5,0.3,Glass +23,0,5.0,2.5,Dispenser +24,0,0.5,0.3,Sandstone +25,0,5.0,2.5,NoteBlock +35,0,0.5,0.3,WhiteWool +35,1,0.5,0.3,OrangeWool +35,2,0.5,0.3,MagentaWool +35,3,0.5,0.3,LightBlueWool +35,4,0.5,0.3,YellowWool +35,5,0.5,0.3,LightGreenWool +35,6,0.5,0.3,PinkWool +35,7,0.5,0.3,DarkGrayWool +35,8,0.5,0.3,LightGrayWool +35,9,0.5,0.3,CyanWool +35,10,0.5,0.3,PurpleWool +35,11,0.5,0.3,BlueWool +35,12,0.5,0.3,BrownWool +35,13,0.5,0.3,DarkGreenWool +35,14,0.5,0.3,RedWool +35,15,0.5,0.3,BlackWool +37,0,0.3,0.2,YellowFlower +38,0,0.3,0.2,RedRose +39,0,0.3,0.2,BrownMushroom +40,0,0.3,0.2,RedMushroom +45,0,1.0,0.8,BrickBlock +46,0,5.0,2.5,TNT +47,0,2.0,1.0,Bookshelf +50,0,0.5,0.3,Torch +52,0,5000000.0,0.0,MonsterSpawner +54,0,2.0,1.0,Chest +65,0,1.0,0.5,Ladder +66,0,3.0,2.0,Rails +81,0,1.0,0.5,Cactus +84,0,10.0,5.0,Jukebox +86,0,4.0,2.0,Pumpkin +87,0,0.5,0.1,Netherrack +88,0,1.0,0.2,Soulsand +89,0,3.0,1.0,Glowstone +91,0,5.0,3.0,JackOLantern +260,0,2.0,1.0,Apple +261,0,3.0,2.0,Bow +262,0,1.0,0.8,Arrow +263,0,1.0,0.8,Coal +264,0,20.0,10.0,Diamond +265,0,3.0,2.0,IronIngot +266,0,3.0,2.0,GoldIngot +287,0,1.0,0.5,String +288,0,0.5,0.3,Feather +289,0,2.0,1.0,Gunpowder +295,0,0.5,0.3,Seeds +296,0,0.8,0.5,Wheat +297,0,2.0,1.0,Bread +318,0,0.5,0.3,Flint +319,0,1.0,0.5,RawPorkchop +320,0,2.0,1.0,CookedPorkchop +321,0,1.0,1.0,Painting +322,0,30.0,20.0,GoldenApple +325,0,3.0,1.0,Bucket +327,0,5.0,2.0,LavaBucket +331,0,0.5,0.2,Redstone +336,0,0.8,0.5,ClayBrick +337,0,0.3,0.2,ClayBalls +338,0,0.5,0.3,SugarCane +341,0,1.0,0.5,Slimeball +351,0,1.0,0.5,BlackDye +351,1,1.0,0.5,RedDye +351,2,1.0,0.5,GreenDye +351,3,1.0,0.5,CocoaBean +351,4,10.0,5.0,LapisLazuli +351,5,1.0,0.5,PurpleDye +351,6,1.0,0.5,CyanDye +351,7,1.0,0.5,LightGrayDye +351,8,1.0,0.5,GrayDye +351,9,1.0,0.5,PinkDye +351,10,1.0,0.5,LimeDye +351,11,1.0,0.5,YellowDye +351,12,1.0,0.5,LightBlueDye +351,13,1.0,0.5,MagentaDye +351,14,1.0,0.5,OrangeDye +351,15,1.0,0.5,BoneMeal + +267,0,10.0,8.0,ironsword +268,0,2.0,1.0,woodensword +269,0,2.0,1.0,woodenshovel +270,0,2.0,1.0,woodenpickaxe +271,0,2.0,1.0,woodenaxe +272,0,2.0,1.0,stonesword +273,0,2.0,1.0,stoneshovel +274,0,2.0,1.0,stonepickaxe +275,0,2.0,1.0,stoneaxe +278,0,5.0,5.0,diamondpickaxe +280,0,1.0,0.0,stick +281,0,2.0,1.0,bowl +282,0,3.0,2.0,mushroomsoup +257,0,7.0,3.0,Iron Pickaxe +258,0,7.0,3.0,Iron Axe +283,0,7.0,3.0,goldsword +284,0,7.0,3.0,goldshovel +285,0,7.0,3.0,goldpickaxe +286,0,7.0,3.0,goldaxe +290,0,2.0,0.0,woodenhoe +291,0,2.0,1.0,stonehoe +292,0,7.0,3.0,ironhoe +293,0,30.0,10.0,diamondhoe +294,0,7.0,3.0,goldhoe +298,0,3.0,1.0,leatherhelmet +299,0,3.0,1.0,leatherchestplate +300,0,3.0,1.0,leatherleggings +301,0,3.0,1.0,leatherboots +302,0,10.0,5.0,chainmailhelmet +303,0,10.0,5.0,chainmailchestplate +304,0,10.0,5.0,chainmailleggings +305,0,10.0,5.0,chainmailboots +306,0,20.0,10.0,ironhelmet +307,0,20.0,10.0,ironchestplate +308,0,20.0,10.0,ironleggings +309,0,20.0,10.0,ironboots +310,0,80.0,30.0,diamondhelmet +311,0,80.0,30.0,diamondchestplate +312,0,80.0,30.0,diamondleggings +313,0,80.0,30.0,diamondboots +314,0,20.0,10.0,goldhelmet +315,0,20.0,10.0,goldchestplate +316,0,20.0,10.0,goldleggings +317,0,20.0,10.0,goldboots +323,0,1.0,0.5,sign +328,0,5.0,3.0,minecart +329,0,5.0,3.0,saddle +332,0,0.5,0.2,snowball +333,0,2.0,1.0,boat +18,,0.5,0.3,Leaves +22,,70,50,Lapis Lazuli Block +25,,5,2.5,Note Block +27,,3.0,1.0,Powered rail +28,,3.0,1.0,Detector rail +29,,3.0,2.0,Sticky Piston +30,,3.0,1.0,Web +33,,3.0,2.0,Piston +41,,70,50,Gold Block +42,,70,50,Iron Block +44,,0.5,0.2,Stone Slab +44,1,0.5,0.3,Sandstone Slab +44,2,0.5,0.2,Wooden Slab +44,3,0.5,0.2,Cobble Slab +48,,10.0,5.0,Mossy Cobblestone +49,,30.0,10.0,Obsidian +53,,0.8,0.5,Wooden Stairs +58,,2.0,1.0,Workbench +61,,2.0,1.0,Furnace +67,,0.8,0.5,Cobblestone Stairs +69,,1.0,0.3,Lever +70,,1.0,0.5,Stone Pressure Plate +72,,1.0,0.5,Wooden Pressure Plate +76,,1.0,0.5,Redstone Torch +77,,0.3,0.2,Stone Button +85,,1.0,0.5,Fence +96,,1.0,0.5,Trapdoor +259,,3.0,2.0,Flint and Steel +280,,0.3,0.2,Stick +324,,1.0,0.8,Wooden Door +326,,3.0,2.0,Water Bucket +330,,10.0,5.0,Iron Door +334,,1.0,0.5,Leather +335,,3.0,2.0,Milk Bucket +339,,0.5,0.3,Paper +340,,2.0,1.0,Book +345,,5.0,3.0,Compass +346,,3.0,2.0,Fishing Rod +347,,5.0,3.0,Watch +348,,1.0,0.5,Glowstone Dust +349,,1.0,0.5,Raw Fish +350,,2.0,1.0,Cooked Fish +352,,1.0,0.5,Bone +353,,1.0,0.5,Sugar +354,,2.0,1.0,Cake +355,,2.0,1.0,Bed +356,,3.0,2.0,Redstone Repeater +357,,2.0,1.0,Cookie +358,,5.0,3.0,Map +359,,3.0,2.0,Shears \ No newline at end of file diff --git a/BetterShop_iFearz.csv b/BetterShop_iFearz.csv new file mode 100644 index 0000000..1dff85f --- /dev/null +++ b/BetterShop_iFearz.csv @@ -0,0 +1,89 @@ +id,subdata,buyprice,sellprice,name +1,0,1.0,0.5,Stone +3,0,0.3,0.1,Dirt +4,0,0.5,0.2,Cobblestone +5,0,0.2,0.1,Wood +6,0,0.2,0.1,Sapling +12,0,0.5,0.25,Sand +13,0,0.3,0.25,Gravel +17,0,0.75,0.5,Log +20,0,0.5,0.35,Glass +23,0,7.25,2.5,Dispenser +24,0,1.25,1.15,Sandstone +25,0,2.25,2.0,NoteBlock +35,0,0.25,0.1,WhiteWool +35,1,1.7,0.5,OrangeWool +35,2,4.65,1.5,MagentaWool +35,3,6.55,2.1,LightBlueWool +35,4,1.15,0.3,YellowWool +35,5,1.35,0.4,LightGreenWool +35,6,1.55,0.45,PinkWool +35,7,2.05,0.4,DarkGrayWool +35,8,1.5,0.3,LightGrayWool +35,9,6.9,2.25,CyanWool +35,10,7.15,2.3,PurpleWool +35,11,12.15,4.0,BlueWool +35,12,1.65,0.5,BrownWool +35,13,1.65,0.5,DarkGreenWool +35,14,2.15,0.65,RedWool +35,15,3.15,0.75,BlackWool +37,0,1.0,0.5,YellowFlower +38,0,1.0,0.5,RedRose +39,0,8.0,3.0,BrownMushroom +40,0,15.0,5.0,RedMushroom +45,0,1.5,1.45,BrickBlock +46,0,7.0,4.0,TNT +47,0,1.0,0.7,Bookshelf +50,0,0.95,0.75,Torch +52,0,300.0,0.0,MonsterSpawner +54,0,2.0,0.6,Chest +65,0,0.9,0.6,Ladder +66,0,2.0,1.5,Rails +81,0,0.75,0.25,Cactus +84,0,10.0,6.0,Jukebox +86,0,4.0,2.0,Pumpkin +87,0,0.5,0.1,Netherrack +88,0,0.75,0.2,Soulsand +89,0,20.0,10.0,Glowstone +91,0,5.0,2.5,JackOLantern +260,0,5.0,1.0,Apple +261,0,3.0,2.0,Bow +262,0,1.0,0.8,Arrow +263,0,1.15,0.8,Coal +264,0,75.0,65.0,Diamond +265,0,5.0,5.0,IronIngot +266,0,15.0,15.0,GoldIngot +287,0,0.85,0.75,String +288,0,0.5,0.25,Feather +289,0,1.5,0.75,Gunpowder +295,0,0.25,0.15,Seeds +296,0,0.75,0.5,Wheat +297,0,2.5,1.5,Bread +318,0,0.45,0.3,Flint +319,0,1.0,0.45,RawPorkchop +320,0,2.0,0.9,CookedPorkchop +321,0,1.1,0.75,Painting +322,0,30.0,25.0,GoldenApple +325,0,15.5,15.0,Bucket +327,0,25.0,17.0,LavaBucket +331,0,0.5,0.25,Redstone +336,0,0.4,0.3,ClayBrick +337,0,0.19,0.17,ClayBalls +338,0,3.0,0.1,SugarCane +341,0,5.0,3.0,Slimeball +351,0,3.0,2.5,BlackDye +351,1,2.0,1.75,RedDye +351,2,1.5,1.25,GreenDye +351,3,1.5,1.0,CocoaBean +351,4,12.0,10.0,LapisLazuli +351,5,7.0,6.25,PurpleDye +351,6,6.25,5.0,CyanDye +351,7,1.35,0.85,LightGrayDye +351,8,1.9,1.25,GrayDye +351,9,1.4,1.0,PinkDye +351,10,1.2,0.5,LimeDye +351,11,1.0,0.75,YellowDye +351,12,6.4,5.0,LightBlueDye +351,13,4.5,3.25,MagentaDye +351,14,1.55,1.3,OrangeDye +351,15,0.75,0.3,BoneMeal diff --git a/BetterShop_jascotty2.csv b/BetterShop_jascotty2.csv new file mode 100644 index 0000000..109184b --- /dev/null +++ b/BetterShop_jascotty2.csv @@ -0,0 +1,93 @@ +id,subdata,buyprice,sellprice,name +1,0,1.0,0.5,stone +2,0,5.0,1.0,grass +3,0,0.4,0.1,dirt +4,0,0.5,0.2,cobblestone +5,0,0.2,0.1,wood +6,0,0.2,0.1,sapling +12,0,0.3,0.25,sand +13,0,0.3,0.25,gravel +17,0,0.75,0.5,log +19,0,30.0,10.0,sponge +20,0,0.5,0.35,glass +23,0,7.25,2.5,dispenser +24,0,1.25,1.15,sandstone +25,0,2.25,2.0,noteblock +35,0,0.25,0.1,whitecloth +35,1,1.7,0.5,orangecloth +35,2,4.65,1.5,magentacloth +35,3,6.55,2.1,lightbluecloth +35,4,1.15,0.3,yellowcloth +35,5,1.35,0.4,lightgreencloth +35,6,1.55,0.45,pinkcloth +35,7,2.05,0.4,darkgraycloth +35,8,1.5,0.3,lightgraycloth +35,9,6.9,2.25,cyancloth +35,10,7.15,2.3,purplecloth +35,11,12.15,4.0,bluecloth +35,12,1.65,0.5,browncloth +35,13,1.65,0.5,darkgreencloth +35,14,2.15,0.65,redcloth +35,15,3.15,0.75,blackcloth +37,0,2.0,1.5,yellowflower +38,0,4.0,3.0,redrose +39,0,8.0,7.0,brownmushroom +40,0,15.0,14.0,redmushroom +45,0,1.5,1.45,brickblock +46,0,9.5,8.0,tnt +47,0,1.0,0.7,bookshelf +50,0,0.95,0.75,torch +54,0,2.0,0.6,chest +65,0,0.9,0.6,ladder +66,0,2.0,1.5,minecarttrack +81,0,0.75,0.25,cactus +84,0,10.0,8.0,jukebox +86,0,4.0,2.0,pumpkin +87,0,0.5,0.1,netherack +88,0,0.75,0.2,soulsand +89,0,20.0,15.0,glowstone +91,0,5.0,2.5,jackolantern +260,0,5.0,1.0,apple +261,0,3.0,2.0,bow +262,0,1.0,0.8,arrow +263,0,1.15,0.8,coal +264,0,75.0,75.0,diamond +265,0,5.0,5.0,ironingot +266,0,15.0,15.0,goldingot +287,0,0.85,0.75,string +288,0,0.5,0.25,feather +289,0,1.5,0.75,gunpowder +295,0,0.25,0.15,seeds +296,0,0.75,0.5,wheat +297,0,2.5,1.5,bread +302,0,90.0,-1.0,chainmailhelmet +303,0,144.0,-1.0,chainmailchestplate +304,0,126.0,-1.0,chainmailleggings +305,0,72.0,-1.0,chainmailboots +318,0,0.45,0.3,flint +319,0,1.0,0.45,pork +320,0,2.0,0.9,grilledpork +321,0,1.1,0.75,painting +322,0,15.0,10.0,goldenapple +325,0,15.5,15.0,bucket +327,0,25.0,17.0,lavabucket +331,0,0.5,0.25,redstone +336,0,0.4,0.3,claybrick +337,0,0.19,0.17,clayball +338,0,3.0,0.1,sugarcane +351,0,3.0,2.5,blackdye +351,1,2.0,1.75,reddye +351,2,1.5,1.25,greendye +351,3,1.5,1.0,browndye +351,4,12.0,10.0,bluedye +351,5,7.0,6.25,purpledye +351,6,6.25,5.0,tealdye +351,7,1.35,0.85,lightgraydye +351,8,1.9,1.25,graydye +351,9,1.4,1.0,pinkdye +351,10,1.2,0.5,limedye +351,11,1.0,0.75,yellowdye +351,12,6.4,5.0,lightbluedye +351,13,4.5,3.25,magentadye +351,14,1.55,1.3,orangedye +351,15,0.75,0.3,bonemeal diff --git a/BetterShop_jjfs85.csv b/BetterShop_jjfs85.csv new file mode 100644 index 0000000..cadcf72 --- /dev/null +++ b/BetterShop_jjfs85.csv @@ -0,0 +1,145 @@ +id,subdata,buyprice,sellprice,name +1,0,2.0,2.0,stone +2,0,2.0,2.0,grass +3,0,1.0,1.0,dirt +5,0,1.0,1.0,wood +6,0,1.0,1.0,sapling +7,0,1000000.0,0.0,bedrock +8,0,3.0,2.0,water +10,0,5.0,3.0,lava +12,0,1.0,1.0,sand +13,0,1.0,1.0,gravel +14,0,15.0,10.0,goldore +15,0,4.0,3.0,ironore +16,0,3.0,2.0,coalore +17,0,3.0,2.0,log +18,0,4.0,2.0,leaves +19,0,10.0,0.0,sponge +20,0,2.0,1.0,glass +21,0,8.0,0.0,lapislazuliore +22,0,12.0,10.0,lapislazuliblock +23,0,16.0,14.0,dispenser +24,0,1.0,1.0,sandstone +25,0,10.0,8.0,noteblock +35,0,2.0,1.0,whitewool +35,5,3.0,4.0,lightgreenwool +35,11,1.0,-1.0,bluewool +37,0,2.0,2.0,yellowflower +38,0,3.0,2.0,redrose +39,0,2.0,2.0,brownmushroom +40,0,2.0,2.0,redmushroom +41,0,150.0,140.0,goldblock +42,0,40.0,35.0,ironblock +43,0,12.0,0.0,doublestep +44,0,6.0,4.0,step +45,0,4.0,4.0,brickblock +47,0,10.0,9.0,bookshelf +48,0,8.0,8.0,mossycobblestone +49,0,50.0,50.0,obsidian +50,0,1.0,0.0,torch +52,0,1000000.0,0.0,mobspawner +53,0,1.0,1.0,woodenstairs +54,0,8.0,4.0,chest +56,0,55.0,0.0,diamondore +57,0,495.0,475.0,diamondblock +58,0,4.0,1.0,workbench +61,0,8.0,4.0,furnace +64,0,6.0,3.0,woodendoorhalf +65,0,3.0,2.0,ladder +66,0,32.0,30.0,minecarttrack +67,0,6.0,3.0,cobblestonestairs +69,0,2.0,2.0,lever +70,0,6.0,5.0,stonepressureplate +71,0,30.0,20.0,irondoorhalf +72,0,3.0,2.0,woodenpressureplate +73,0,2.0,1.0,redstoneore +75,0,1.0,1.0,redstonetorchoff +77,0,1.0,1.0,stonebutton +78,0,1.0,0.0,snowcovering +79,0,3.0,0.0,ice +80,0,3.0,0.0,snowblock +81,0,1.0,1.0,cactus +82,0,5.0,3.0,clayblock +83,0,2.0,1.0,sugarcaneblock +84,0,58.0,56.0,jukebox +85,0,3.0,1.0,fence +86,0,5.0,4.0,pumpkin +87,0,1.0,0.0,netherack +88,0,1.0,0.0,soulsand +89,0,2.0,0.0,glowstone +90,0,1000.0,0.0,portal +91,0,6.0,6.0,jackolantern +92,0,10.0,10.0,cakeblock +256,0,6.0,4.0,ironshovel +257,0,16.0,12.0,ironpickaxe +258,0,16.0,12.0,ironaxe +260,0,5.0,5.0,apple +261,0,16.0,16.0,bow +262,0,1.0,0.0,arrow +263,0,2.0,1.0,coal +264,0,52.0,42.0,diamond +265,0,6.0,4.0,ironingot +266,0,18.0,16.0,goldingot +267,0,11.0,10.0,ironsword +268,0,2.0,0.0,woodensword +269,0,2.0,0.0,woodenshovel +270,0,4.0,0.0,woodenpickaxe +271,0,4.0,0.0,woodenaxe +272,0,3.0,1.0,stonesword +273,0,2.0,1.0,stoneshovel +274,0,4.0,2.0,stonepickaxe +275,0,4.0,2.0,stoneaxe +278,0,5.0,5.0,diamondpickaxe +280,0,1.0,0.0,stick +281,0,3.0,3.0,bowl +282,0,7.0,7.0,mushroomsoup +283,0,30.0,28.0,goldsword +284,0,16.0,14.0,goldshovel +285,0,45.0,42.0,goldpickaxe +286,0,45.0,42.0,goldaxe +287,0,5.0,5.0,string +288,0,1.0,1.0,feather +289,0,1000000.0,100.0,gunpowder +290,0,3.0,0.0,woodenhoe +291,0,3.0,1.0,stonehoe +292,0,11.0,7.0,ironhoe +293,0,110.0,100.0,diamondhoe +294,0,32.0,27.0,goldhoe +295,0,1.0,0.0,seeds +296,0,6.0,6.0,wheat +297,0,20.0,18.0,bread +298,0,5.0,5.0,leatherhelmet +299,0,8.0,8.0,leatherchestplate +300,0,7.0,7.0,leatherleggings +301,0,4.0,4.0,leatherboots +302,0,100.0,0.0,chainmailhelmet +303,0,160.0,0.0,chainmailchestplate +304,0,140.0,0.0,chainmailleggings +305,0,80.0,0.0,chainmailboots +306,0,30.0,25.0,ironhelmet +307,0,45.0,40.0,ironchestplate +308,0,40.0,35.0,ironleggings +309,0,25.0,20.0,ironboots +310,0,300.0,250.0,diamondhelmet +311,0,450.0,400.0,diamondchestplate +312,0,400.0,350.0,diamondleggings +313,0,250.0,200.0,diamondboots +314,0,75.0,70.0,goldhelmet +315,0,120.0,115.0,goldchestplate +316,0,105.0,98.0,goldleggings +317,0,45.0,42.0,goldboots +318,0,2.0,2.0,flint +319,0,3.0,3.0,pork +320,0,6.0,6.0,grilledpork +321,0,50.0,20.0,painting +322,0,125.0,125.0,goldenapple +323,0,7.0,3.0,sign +325,0,15.0,12.0,bucket +328,0,25.0,15.0,minecart +329,0,1000000.0,10000.0,saddle +331,0,1.0,1.0,redstone +332,0,1.0,1.0,snowball +333,0,5.0,3.0,boat +344,0,2.0,1.0,egg +351,10,2.0,2.0,limedye +5000,0,1400.0,-1.0,diamondarmor diff --git a/PriceList.yml b/PriceList.yml deleted file mode 100644 index 5d5232d..0000000 --- a/PriceList.yml +++ /dev/null @@ -1,557 +0,0 @@ -prices: - item1: - name: stone - buy: 2 - sell: 2 - item2: - name: grass - buy: 2 - sell: 2 - item3: - name: dirt - buy: 1 - sell: 1 - item5: - name: wood - buy: 1 - sell: 1 - item6: - name: sapling - buy: 1 - sell: 1 - item7: - name: bedrock - buy: 1000000 - sell: 0 - item8: - name: water - buy: 3 - sell: 2 - item10: - name: lava - buy: 5 - sell: 3 - item12: - name: sand - buy: 1 - sell: 1 - item13: - name: gravel - buy: 1 - sell: 1 - item14: - name: gold_ore - buy: 15 - sell: 10 - item15: - name: iron_ore - buy: 4 - sell: 3 - item16: - name: coal_ore - buy: 3 - sell: 2 - item17: - name: log - buy: 3 - sell: 2 - item18: - name: leaves - buy: 4 - sell: 2 - item19: - name: sponge - buy: 10 - sell: 0 - item20: - name: glass - buy: 2 - sell: 1 - item21: - name: lapis_ore - buy: 8 - sell: 0 - item22: - name: lapis_block - buy: 12 - sell: 10 - item23: - name: dispenser - buy: 16 - sell: 14 - item24: - name: sandstone - buy: 1 - sell: 1 - item25: - name: note_block - buy: 10 - sell: 8 - item35: - name: wool - buy: 2 - sell: 1 - item37: - name: yellow_flower - buy: 2 - sell: 2 - item38: - name: red_rose - buy: 3 - sell: 2 - item39: - name: brown_mushroom - buy: 2 - sell: 2 - item40: - name: red_mushroom - buy: 2 - sell: 2 - item41: - name: gold_block - buy: 150 - sell: 140 - item42: - name: iron_block - buy: 40 - sell: 35 - item43: - name: double_step - buy: 12 - sell: 0 - item44: - name: step - buy: 6 - sell: 4 - item45: - name: brick - buy: 4 - sell: 4 - item47: - name: bookshelf - buy: 10 - sell: 9 - item48: - name: mossy_cobblestone - buy: 8 - sell: 8 - item49: - name: obsidian - buy: 50 - sell: 50 - item50: - name: torch - buy: 1 - sell: 0 - item52: - name: mob_spawner - buy: 1000000 - sell: 0 - item53: - name: wood_stairs - buy: 1 - sell: 1 - item54: - name: chest - buy: 8 - sell: 4 - item56: - name: diamond_ore - buy: 55 - sell: 0 - item57: - name: diamond_block - buy: 495 - sell: 475 - item58: - name: workbench - buy: 4 - sell: 1 - item61: - name: furnace - buy: 8 - sell: 4 - item64: - name: wooden_door - buy: 6 - sell: 3 - item65: - name: ladder - buy: 3 - sell: 2 - item66: - name: rails - buy: 32 - sell: 30 - item67: - name: cobblestone_stairs - buy: 6 - sell: 3 - item69: - name: lever - buy: 2 - sell: 2 - item70: - name: stone_plate - buy: 6 - sell: 5 - item71: - name: iron_door_block - buy: 30 - sell: 20 - item72: - name: wood_plate - buy: 3 - sell: 2 - item73: - name: redstone_ore - buy: 2 - sell: 1 - item75: - name: redstone_torch_off - buy: 1 - sell: 1 - item77: - name: stone_button - buy: 1 - sell: 1 - item78: - name: snow - buy: 1 - sell: 0 - item79: - name: ice - buy: 3 - sell: 0 - item80: - name: snow_block - buy: 3 - sell: 0 - item81: - name: cactus - buy: 1 - sell: 1 - item82: - name: clay - buy: 5 - sell: 3 - item83: - name: sugar_cane_block - buy: 2 - sell: 1 - item84: - name: jukebox - buy: 58 - sell: 56 - item85: - name: fence - buy: 3 - sell: 1 - item86: - name: pumpkin - buy: 5 - sell: 4 - item87: - name: netherrack - buy: 1 - sell: 0 - item88: - name: soul_sand - buy: 1 - sell: 0 - item89: - name: glowstone - buy: 2 - sell: 0 - item90: - name: portal - buy: 1000 - sell: 0 - item91: - name: jack_o_lantern - buy: 6 - sell: 6 - item92: - name: cake_block - buy: 10 - sell: 10 - item256: - name: iron_spade - buy: 6 - sell: 4 - item257: - name: iron_pickaxe - buy: 16 - sell: 12 - item258: - name: iron_axe - buy: 16 - sell: 12 - item260: - name: apple - buy: 5 - sell: 5 - item261: - name: bow - buy: 16 - sell: 16 - item262: - name: arrow - buy: 1 - sell: 0 - item263: - name: coal - buy: 2 - sell: 1 - item264: - name: diamond - buy: 52 - sell: 42 - item265: - name: iron_ingot - buy: 6 - sell: 4 - item266: - name: gold_ingot - buy: 18 - sell: 16 - item267: - name: iron_sword - buy: 11 - sell: 10 - item268: - name: wood_sword - buy: 2 - sell: 0 - item269: - name: wood_spade - buy: 2 - sell: 0 - item270: - name: wood_pickaxe - buy: 4 - sell: 0 - item271: - name: wood_axe - buy: 4 - sell: 0 - item272: - name: stone_sword - buy: 3 - sell: 1 - item273: - name: stone_spade - buy: 2 - sell: 1 - item274: - name: stone_pickaxe - buy: 4 - sell: 2 - item275: - name: stone_axe - buy: 4 - sell: 2 - item280: - name: stick - buy: 1 - sell: 0 - item281: - name: bowl - buy: 3 - sell: 3 - item282: - name: mushroom_soup - buy: 7 - sell: 7 - item283: - name: gold_sword - buy: 30 - sell: 28 - item284: - name: gold_spade - buy: 16 - sell: 14 - item285: - name: gold_pickaxe - buy: 45 - sell: 42 - item286: - name: gold_axe - buy: 45 - sell: 42 - item287: - name: string - buy: 5 - sell: 5 - item288: - name: feather - buy: 1 - sell: 1 - item289: - name: sulphur - buy: 1000000 - sell: 100 - item290: - name: wood_hoe - buy: 3 - sell: 0 - item291: - name: stone_hoe - buy: 3 - sell: 1 - item292: - name: iron_hoe - buy: 11 - sell: 7 - item293: - name: diamond_hoe - buy: 110 - sell: 100 - item294: - name: gold_hoe - buy: 32 - sell: 27 - item295: - name: seeds - buy: 1 - sell: 0 - item296: - name: wheat - buy: 6 - sell: 6 - item297: - name: bread - buy: 20 - sell: 18 - item298: - name: leather_helmet - buy: 5 - sell: 5 - item299: - name: leather_chestplate - buy: 8 - sell: 8 - item300: - name: leather_leggings - buy: 7 - sell: 7 - item301: - name: leather_boots - buy: 4 - sell: 4 - item302: - name: chainmail_helmet - buy: 100 - sell: 0 - item303: - name: chainmail_chestplate - buy: 160 - sell: 0 - item304: - name: chainmail_leggings - buy: 140 - sell: 0 - item305: - name: chainmail_boots - buy: 80 - sell: 0 - item306: - name: iron_helmet - buy: 30 - sell: 25 - item307: - name: iron_chestplate - buy: 45 - sell: 40 - item308: - name: iron_leggings - buy: 40 - sell: 35 - item309: - name: iron_boots - buy: 25 - sell: 20 - item310: - name: diamond_helmet - buy: 300 - sell: 250 - item311: - name: diamond_chestplate - buy: 450 - sell: 400 - item312: - name: diamond_leggings - buy: 400 - sell: 350 - item313: - name: diamond_boots - buy: 250 - sell: 200 - item314: - name: gold_helmet - buy: 75 - sell: 70 - item315: - name: gold_chestplate - buy: 120 - sell: 115 - item316: - name: gold_leggings - buy: 105 - sell: 98 - item317: - name: gold_boots - buy: 45 - sell: 42 - item318: - name: flint - buy: 2 - sell: 2 - item319: - name: pork - buy: 3 - sell: 3 - item320: - name: grilled_pork - buy: 6 - sell: 6 - item321: - name: painting - buy: 50 - sell: 20 - item322: - name: golden_apple - buy: 125 - sell: 125 - item323: - name: sign - buy: 7 - sell: 3 - item325: - name: bucket - buy: 15 - sell: 12 - item328: - name: minecart - buy: 25 - sell: 15 - item329: - name: saddle - buy: 1000000 - sell: 10000 - item331: - name: redstone - buy: 1 - sell: 1 - item332: - name: snow_ball - buy: 1 - sell: 1 - item333: - name: boat - buy: 5 - sell: 3 - item344: - name: egg - buy: 2 - sell: 1 diff --git a/README b/README index 48e7dbf..40d90bb 100644 --- a/README +++ b/README @@ -1,16 +1,219 @@ -To build, you need to include the following as libraries: - bukkit.jar - iConomy.jar - permissions.jar +BetterShop - The "better" global command-based shop that ties into iConomy -If you want helpful bukkit javadocs, you should get: - the Bukkit source code +active developer: jascotty2 +initial idea & programing by jjfs85 +with additional coding contributions by: lologarithm, dhatch +=============================================================================== +In-Game Commands: +=============================================================================== -Once it builds, compress on linux using: - jar or an equivalent archiver - jenny:~/workspace/BetterShop/bin$ jar -cf BetterShop.jar * +/shop for those used to a plugin having one command, this is another way to get to the following (also used for some admin commands) +/shop ver[sion] shows version # and if is up-to-date +/shop backup backup the database to a csv file +/shop import insert values from file in plugin folder +/shop restore clear shop & load from saved backup file +/shop restock manually update stock (if enabled) +/shop update OP-only command that forces update & restarts server plugins +/shop region define uses the player's WorldEdit selection to define a shop region +/shop region remove remove a region definition +/shop region list [page] list current regions +/shop chest define set the chest in the crosshairs to a shop chest +/shop chest edit open the shop chest for editing +/shop chest remove remove the chest shop status +/shophelp (shelp) [command] shows you all the commands you can use, or more help on [command] +/shoplist (sl,slist) [pagenum] shows a listing of items for sale (if -1, all, or full given, will show full list. (for console use)) +/shopitems (sitems) show full listing of items in shop, without prices +/shopcheck (sc,scheck) [amount] lookup a specific item so you don't have to read through pages of prices +/shopbuy (buy,sbuy) [amount] buy an item for the price in the shopshop ("all" is accepted as an amount) +/shopbuyall (buyall,sbuyall) buy all of an item that you can hold +/shopsell (sell,ssell) [amount] sell an item for the price in the shop +/shopadd (sadd): [sell] add an item to or update an item in the price list +/shopremove (sremove) remove an item from the price list +/shopload (sload) reload prices from pricelist database +/shopsellall (sellall) [inv] [item [...]] Sell all of item from your inventory (alias to command /shop sell all) (inv will not search lower 9 slots) +/shopbuystack (buystack) [item | amount] buy a stack of an item (usually 64) +/shopsellstack (sellstack) [item | amount] sell a stack of an item (usually 64) +/shopbuyagain (buyagain,sba) repeat last buy action +/shopsellagain (sellagain,ssa) repeat last sell action +/shopkits (skits) show a listing of available kits (will be elaborated later) +/shop alias show the itemname, with a list of aliases -or on Windows using: - I have no clue. I guess something similar to jar. -And it's ready to be placed in a plugins directory :) +=============================================================================== +Features: +=============================================================================== + +=== plugin support +currently dependent on +- iConomy http://dev.bukkit.org/server-mods/iconomy/ +--or +- BOSeconomy http://dev.bukkit.org/server-mods/boseconomy/ +-- or +- MultiCurrency http://forums.bukkit.org/threads/26455/ +--or +- Essentials http://dev.bukkit.org/server-mods/essentials/ +--or +- 3co http://forums.bukkit.org/threads/22461/ +also supports: +Permissions http://forums.bukkit.org/threads/18430/ +Help http://forums.bukkit.org/threads/13601/ +MinecraftIM http://dev.bukkit.org/server-mods/minecraftim/ +Spout http://dev.bukkit.org/server-mods/spout/ + +=== configurable +Fully configurable colors and message text in config.yml file +shoplist can have text alignment (read config for more info): +- uses minecraft font character spacing, so is very close to perfectly aligned in chat +shoplist can optionally not show listing tail (or head, but recommended to leave of ) +Items can be colored in the new itemsdb.yml file +also in itemsdb.yml: kits! +- define your own kits that the shop can sell +- three examples provided, edit and add to your liking +Configurable Options: +-max pagesize when printing shoplist +-whether to broadcast all transactions publicly +-name of the pricelist file/table +-customsort: a custom sorting order, so you can have items at the top of the shop list +-allowbuyillegal: if someone without BetterShop.admin.illegal can buy illegal items +-whether maxstack should be honored +-if used tools can be bought back +-default color for items + +=== shop flexibility +shopcheck will run a name comparison check, and return all matching items +Item sub-type support for dye colors, cloth colors, etc: magentacloth = 35:2 = cloth:magenta +"all" is a valid amount when buying or selling: "/sell cobblestone all" or "/sell all cobblestone" +damaged tools can be resold for an adjusted value of sellprice*(1-(damage/maxdamage)) +buystack can be given multiple items, or number of stacks: "/buystack wool 5" or "/buystack wool blackdye reddye" +shopsellall can be given multiple items (/sell all cobble gravel flint dirt) +plural-insensitive items: if not found, will check if plural & remove "s" +not just items can be bought anymore: can now buy LivingEntities, like dogs (wolf) +item categories +- can search for items by category (in shoplist) +signs can be used for buying & selling +- first line: [BetterShop] (not case-sensitive) +- second line: action: buy [amt], buyall, buystack, sell [amt], sellall, sellstack +- third line: item (name, id, etc) +- fourth line: unused (was going to make price, but decided on pricecheck instead) +- to activate, someone with BetterShop.admin.makesign must left-click the sign.. the first line will change color when active +- left-click for price check (or activate), right-click to buy/sell +- if a unauthorized player destroys a block a sign is on, is canceled +chests can also be used as a shop interface +- to define a chest, point at the chest & type "/shop chest define" +- automatically detects double-chests +- multiple users can use the same chest shop +- - each user gets a customized chest screen +- - items in the display show either max stack, or max of that item the user can afford +players using Spout (if installed & enabled) can use a GUI Menu for buying & selling +- default button is 'b' +- displays items in shop in top half +- item detail below with # in stock, buy/sell price, & buttons to buy & sell +- amount to buy/sell can be set in text box or with up/down buttons +- item categories can also be used in the display + + +=== administration +Every item and subtype can be priced differently +Disable buying or selling of an item by giving it a price of -1 (0 makes it free) +command aliases to stop carpal tunnel +many commands have sub-aliases.. eg. shoplist kits will run shoplistkits +MySQL pricelist support +MySQL pricelist can be cached for a given timespan (decreases table selects) (flatfile is cached until manually updated) +Transaction records (MySQL or flatfile as .csv) +Downloads mysql-bin.jar dependency automatically +buy/sell cap in program set to 999,999,999 (not that you'd be using that much, but the cap is to prevent other errors) +if encounters errors while editing a player's account, will attempt to reload iConomy (i've had issuses with it before) +can backup the current pricelist: /shop backup +can restore from backup: /shop restore +can import new prices in a batch from a csv (or old yml format) : /shop import +on start, can check the download page to see if there is an update available +shop can be given a finite stock from which to buy & sell +/shop ver[sion] to check the current version & see if there's an update +/shop update to manually download & install the most recent version +MinecraftIM support: forward errors or all messages +checks for missing & unused configuration nodes +strings have default values if missing +auto error reporting added (can be disabled) +custom message you can send with the error report +help main page integration can be disabled +global shop can be disabled & only allow signs to be used +can use permissions to define discounts for certain users +- uses nodes in "BetterShop.discount.xxxx" +- nodes defined in config under discountGroups +- ex: discountGroups: + VIP: 10 # gives 10% discount to players with BetterShop.discount.VIP +support for bukkit permissions +- built-in support for "BetterShop.(user|admin).*" nodes, so you don't have to add all of the other nodes + + +There are two ways to add things to the shop: +Recommended, but slow: use /shopadd [item] [buy-price] [sell-price] +Faster: use a text editor (or spreadsheet program) to add items to the BetterShop.csv file. + items are in this order: id, subdata, buy, sell, name + item id & subdata are checked against itemsdb.yml, and are removed if they don't exist + for your convenience, you can use item names (not subitem names, though), so "wool,blue,1,-1" is a valid entry + The name value is there only for human readability, it's not read by the plugin. + +=============================================================================== +Permissions: +=============================================================================== +Just add the following nodes to Permissions' config.yml file (or data.yml file for GroupManager): + +BetterShop.user.* Allows the user to use the list, sell, buy, and help commands +BetterShop.admin.* Allows the user to use the add, remove, and load commands + +There are other nodes that allow only more specific permissions, but I recommend using the above. + +BetterShop.user.list look through shop listing of prices +BetterShop.user.check check the price of item(s) +BetterShop.user.help view ingame help menu +BetterShop.user.buy buy items from the shop +BetterShop.user.sell sell items to the shop +BetterShop.user.spout for spout screen access +BetterShop.user.chest allow a user to use the a chest shop +BetterShop.admin.add add/edit items to/in the shop +BetterShop.admin.remove remove items from the shop +BetterShop.admin.load reload configuration & pricelist +BetterShop.admin.info show shop stats (only version number right now) +BetterShop.admin.illegal gives the ability to purchase 'illegal' items +BetterShop.admin.backup backing up and restoring the pricelist +BetterShop.admin.restock manually restock (if item stock is enabled) +BetterShop.admin.makesign make/remove a bettershop sign +BetterShop.admin.region define/remove shop regions +BetterShop.admin.chests define/remove chest shops + +=============================================================================== +TODO (items are removed as they're added) +=============================================================================== + +calculated/derived item prices +/shopvalue item[@amount] item to show the current trade value for two items +dynamic market pricing, based off of logged market activity +add option for amount in stock for surplus decreases how much item is worth to the shop +add pages for /shopitems +more descriptive kit listing, similar to shoplist: +- pages +- show what makes up a kit +- enable admin.add to see available kits +SQLite support? +tie item prices to stock quotes? +craftables' stock is tied to source material stock (if using sell craftables) +make LivingEntities follow the owner around? +change buystock if itemStock is enabled so that if trying to buy multiple stacks (buystack item 20) and stock runs out to stop buying (stop showing "out of stock") +individually-customizable restock stock values per-item +automatically add missing config nodes (preserving comments) when upgrading +add a simple builtin economy system, if none found? +user-owned shops (signs, linked to chests?) ? +daily profit caps for selling to the shop +multiple shop definitions for various regions? + + +=== Removed (not going to implement) +persistentMySQL: if false, will disconnect from db when now using it (use if have a high cacheUpdate) + (the MySQL connection is now shared with other features, so disonnecting could cause more performance issues) +dynamicDiscount: if a user has an applicable discount, will also discount items crafted using discount + (if i do implement discounts, this will be automatic) +Cookies integration: http://forums.bukkit.org/threads/5327/ + (might implement if becomes active again) +items as currency (if no economy plugin) + (as i began to write this, i realized that it's an entire economy plugin... one that i just might develop) diff --git a/changelog b/changelog new file mode 100644 index 0000000..1c091dc --- /dev/null +++ b/changelog @@ -0,0 +1,663 @@ +=============================================================================== +BetterShop Changelog: (the (?) means plugin didn't report that as the version.. sorry :)) +=============================================================================== + +Version 2.1.6.4 - 5/6/13 +updated for MC 1.6.1 + + +Version 2.1.6.3 - 5/6/13 +updated for MC 1.5.2 + + +Version 2.1.6.2 - 2/15/13 +added 'Enchanted Golden Apple" to item definitions + + +Version 2.1.6.1 - 2/12/13 +Fixed Spout Button text overlay problem + + +Version 2.1.6 - 2/8/13 +Updated to work with craftbukkit 1.4.7-R1 +removed MinecraftIM error linking +removed remote error tracking +removed auto updating +removed auto library download if using MySQL +fixed erroneous buy price display when buying from a category & can't afford an item +fixed buyall category to properly buy all items that the user can buy +changed debit/credit to ignore negative amounts, in case of any other negative bugs +changed tracking to mcstats: https://mcstats.org/plugin/BetterShop + + +Version 2.1.5.1 - 12/23/12 +Updated to work with craftbukkit 1.4.6-R0.1 +added an admin debug command - for listing the player's inventory + + +Version 2.1.5 - 12/19/12 +Updated to work with craftbukkit 1.4.5-R1.0 + + +Version 2.1.4.4 - 11/26/12 +Changed internal name for heads definitions - was causing name conflicts + + +Version 2.1.4.3 - 11/16/12 +Updated internal item definitions - should resolve unknown item errors + + +Version 2.1.4.2 - 11/10/12 +fixed a vault-specific logic error that caused purchases to be free + + +Version 2.1.4.1 - 11/5/12 +quick fix to work with 1.4 + + +Version 2.1.4 - 7/12/12 (actually 5/4/12.. sorry about that) +Fixed misleading error message from spamming console +- was caused from checking if empty-string bank exists in vault +added multi-discount checking: the highest of the player's groups' discount is applied + + +Version 2.1.3 - 3/13/12 +updated chest methods for new bukkit system +added Vault support +added new items + + +Version 2.1.2 - 3/5/12 +updated methods for bukkit 1.2.3-R0.2 and SpoutPlugin 474 +note - double chest shops glitch the gui, don't know why + + +Version 2.1.1 - 1/27/12 +updated register to 1.5 (adds 3co) +added spawn eggs to the shop +fixed an item return error when trying to sell to a chest +updated bukkit listeners to new methods +fixed permission for shopcheck (was a capitalization issue) +fixed error messages relating to stock +fixed category purchasing bypassing stock checks + + +Version 2.1.0 - 1/10/12 +items & entities updated for 1.0 (craftbukkit 1597) +fixed a bug with added items sometimes registering -1 max +added discount selling options, but recommend leaving at default +removed bukkit command-based permissions.. user.* wasn't working +changed permissions error handling.. shouldn't spam more than one error for a nested command +- (and none for a usage lookup) +added option to replace econ with exp or total exp (which is used in enchantments) +TODO - or (if not enabled) allow exp buy/sell +removed ftp stats tracking snippet (added in 1.6.7) +- changed to another i found with a web interface +- http://pluginstats.randomappdev.com/plugin.aspx?pid=27 +- still waiting on bukkit for their implementation... +fixed non-data items, like pistons.. +fixed a new spout item gui display error (added an enable delay timer) +fixed an itemstack addition bug with data values +fixed sign buy/sell with categories + + +Version 2.0.5 - 10/13/11 +added fancy gradient to itempages :) +fixed itemstack null logic error +lowered min wordmatch value for spellchecking +fixed for compatibility with bukkit 941 (cb 1317) +scrollbar removed for now (problems in spoutcraft 564) + + +Version 2.0.4 - 10/6/11 +fixed tool selling sometimes throwing errors +fixed sign custom price maximum calculations +changed spout gui access to only game screen (not inventory, furnace, etc) +removed erroneous error message about commandShop: global +fixed "blue" signs showing up as a "light blue" color + + +Version 2.0.3 - 10/5/11 +fixed spout key in chat in a disabled region causing access deny messages +fixed no access to creating region shops outside of an existing region + + +Version 2.0.2 - 10/3/11 +fixed for compatibility with craftbukkit 1240 + + +Version 2.0.1 - 10/3/11 +Important Update: fixed a buying bug that instead of purchasing items gives free money +fixed compatibility with new spoutcraft +- new bug caused crashes when attempting to display an empty icon +- also caused positioning to change +- there is a new annoying bug with the scrollbar: +- - when using the arrow buttons, the prior position is selected, then updated on the next button press + + +Version 2.0 - 9/24/11 +near-complete rewrite +- more modular, should be easier to add new features +added region-based shop support +- just main shop for now +- config modes: +- - shop.commandShop: global +- - - regions are areas where command-based shop access is disabled +- - shop.commandShop: regions +- - - globally disabled, except for within regions +- - shop.commandShop: both +- - - allows command-based shop access from anywhere +- - shop.commandShop: none +- - - cannot use commands (or spout) to use the shop +- regions are defined by first selecting a region in worldguard +- - after regions have been defined, can put the WorldEdit.jar in the lib folder +now checks from 2 mirrors (still on git) +- not really needed, but the code behind it looks cleaner +finally adopted some license to support open development. +- now the source is licenced under the GNU General Public License +- http://www.gnu.org/licenses/gpl.txt +added pages menu option (default) so spout scrolling goes by page, not column +added a fix for a spout button error +fixed a spout error where may show amount stock can sell, not buy +added category switching (spout) +- can use 'cycle' for a button to cycle through the categories (shows more per-page) +- or use 'tabbed' for buttons at the top that can be selected +chest shop interface added :D +- to define a chest, point at the chest & type "/shop chest define" +- automatically detects double-chests +- multiple users can use the same chest shop +- - each user gets a customized chest screen +- - items in the display show either max stack, or max of that item the user can afford +- setting for "chestSellBar", which allows selling to a chest which is full (without buying anything) +tnt explosion cancelation settings for sign/chest shop added +- should be more efficient than prior method +commandlog format moved in config & added more formatting options +updated Register methods (fixes iCo6 errors) +added a rudimentary spell check routine +- if an item name doesn't match at first, and there is 1 match for closeness, returns that item +sign shop handling rewrote +- should be more efficient +- can now define custom prices for transactions +- - 4th line contains cost, doen't have to just be a number +- - - "1.23", "$1.23", "1.23 Dollars", "$1.23 total" are all acceptable +- - for a given amount, define the total cost +- - if using "sellall" or "buyall", define unit cost +categories have been implemented in buy & sell +- can sell all items in a category, or buy an amount of each item in a category +- ex. "/sellall food" +sign & chest shops are protected from endermenv (if shop.(sign & chest)DestroyProtection = true) +sql routines updated for foreign locales + + +Version 1.6.7.1 - 9/4/11 +added support for bukkit permissions +- built-in support for "BetterShop.(user|admin).*" nodes, so you don't have to add all of the other nodes +added permission node "BetterShop.user.spout" for spout screen access +fixed some lingering integer pricing errors +added a simple permissions-based discounting system +- uses nodes in "BetterShop.discount.xxxx" +- nodes defined in config under discountGroups +- ex: discountGroups: + VIP: 10 # gives 10% discount to players with BetterShop.discount.VIP +fixed gui defaulting to amount limit when one == 0 +fixed server error reporting bug +changed server uuid method, more stable + + +Version 1.6.7 - 9/3/11 +GUI Menu added using Spout :) +- default button is 'b' +- displays items in shop in top half +- item detail below with # in stock, buy/sell price, & buttons to buy & sell +- amount to buy/sell can be set in text box or with up/down buttons +added economy support via Register 2.1 +- now supports iConomy 4,5,6, BOSEcon 6,7, MultiCurrency, EssentialsEco +changes in how error reporting occurs +plugin stat polling added (may be temporary) +- disabled with AutoErrorReporting: false +permissions updated for 3x +sign valid item check fixed +other misc. fixes + + +Version 1.6.6.6 - 7/19/11 +weather "easter egg" removed (was free to "buy" weather events) +updated MySQL parameters to allow some special characters in names +temp. fix for inventory data error until bukkit merges my fix +other misc. null fixes + + +Version 1.6.6.5 - 7/8/11 +fixed unknown bukkit item naming in itemsdb (more update-proof) +fixed custom maxstack having been removed +better handling of iConomy nullpointer errors +fix buying ghasts (also not of "creature") +misc. null fixes + + +Version 1.6.6.4 - 7/3/11 - new thread & future updates by jascotty2 +updated items for mc 1.7 +fixed unknown item auto adding (more update-proof) +fixed itemdb reload logic error +sign destroy protection thread moved to bukkit scheduler +fixed an itemstock setting error +fixed buying slimes (they're not "creatures") +other misc. fixes + + +Version 1.6.6.3b - 6/21/11 - jascotty2 +fixed a bug with buying decimal values with old iconomy +fixed a bug if /itemalias with no alias + + +Version 1.6.6.3 - 6/21/11 - jascotty2 +added the "permission" node to plugin.yml +fixed entity & kits (broken since db rewrite, sorry) +fixed "buyall" to automatically buy max can afford +/shop alias - show the itemname, with a list of aliases +- also added smart word-wrapping to this function + + +Version 1.6.6.2 - 6/20/11 - jascotty2 +misc. null fixes +MySQL pricelist items fixed + + +Version 1.6.6.1 - 6/19/11 - jascotty2 +fixed itemDat error + + +Version 1.6.6 - 6/16/11 - jascotty2 +update version to 1.6.6 (what it should've been) and fix error reporting passwd + + +Version 1.6.6 (1.6.5.7) - 6/16/11 - jascotty2 +updated for mc 1.6.6 (craftbukkit 860) +extracts Essentials economy currency names from its config file +fixed iconomy debit logic error: could buy something for the same cost as your balance for free +major item & kit database restructure - much cleaner structure now +itemsdb.yml reduced - now constant data is embedded in the plugin; the yml file is for extra item descriptions +- now mostly for changing item names and aliases +- some other data can still be changed, like legal & craft can be expanded (if using a custom craft plugin) +- custom data values can no longer be added (which could otherwise cause client crashes) +- - also adds new bukkit items if plugin hasn't been updated (within plugin, not yml file) +items can now have spaces in the main name +added item categories (searchable in shoplist) +- can search for items by category +- categories are entered in a seperate section on itemsdb.yml, instead of editing for every item entry +added an update recursion check; should stop random endless update loops +changed update date check string +Known Bug: bought maps are all map_0 + - waiting on bukkit for a way to remedy this.. +# total of over 56,000 cumulative downloads for the jascotty2 versions of bettershop :D + + +Version 1.6.5.6 - 5/11/11 - jascotty2 +Essentials economy support added + + +Version 1.6.5.5b - 5/11/11 - jascotty2 +fixed iConomy debit/credit low-balance bug + + +Version 1.6.5.5 - 5/11/11 - jascotty2 +hopefully fixed an update restart error +wolves fixed for mc 1.5 +fixed saving errors when directory removed +currency naming fixed +updated for iConomy 1.5, compatibility with 1.4 retained :D +fixed sign creation if sign is colored (eg. if dat file was accidentally deleted) + + +Version 1.6.5.4 - 4/18/11 - jascotty2 +misc. nullpointer fixes +# 14,000+ downloads! + + +Version 1.6.5.3 - 4/11/11 - jascotty2 +added option to disable sign protection +added sign action wait, to prevent sign click spamming +fixed a nullpointer error when selling + + +Version 1.6.5.2 - 4/11/11 - jascotty2 +removed spawnwolf debugging line +wolves bought have correct health + + +Version 1.6.5.1 - 4/10/11 - jascotty2 +removed a debugging line +wolfs bought have their owner already set (need to increase health) +- thanks goes out to ashtonw (jynxdaddy) for working this out: https://github.com/ashtonw/WolfSpawn + + +Version 1.6.5.0 - 4/10/11 - jascotty2 +corrected new settings (missed some previously) +fixed default sign pricecheck showing wrong one +fixed messenger formatting error +misc. null fixes +BOSEconomy support added +changed block protection from scan to save (should improve performance) +added tnt protection, in the form of a scanner that routinely checks that all shop signs & their anchors still exist +added syntax check for /buy & /sell # [item] (like the sign syntax) +allowbuyillegal now defaults to true +fixed color set (hidden logic error) +signs can have the item name colored according to the defined item color + + +Version 1.6.4.2 - 4/9/11 - jascotty2 +fixed sign destroy permission check to only check bettershop signs + + +Version 1.6.4.1 - 4/8/11 - jascotty2 +"inhand" and "inv" added as valid sellall/buyall items (signs only) +- "in hand" buys/sells all of the type of item in the player's hand +sign destroy protection algorithm improved (still can't check for explosions :( ) + + +Version 1.6.4.0 (?) - 4/8/11 - jascotty2 +stock message error fixed when selling too much +fixed causing a server freeze +added amount to pricecheck, so can check the result buy/sell price +fixed a buying error when already has some of the item +added a sign interface +- first line: [BetterShop] (not case-sensitive) +- second line: action: buy [amt], buyall, buystack, sell [amt], sellall, sellstack +- third line: item (name, id, etc) +- fourth line: unused (was going to make price, but decided on pricecheck instead) +- to activate, someone with BetterShop.admin.makesign must left-click the sign.. the first line will change color when active +- left-click to pricecheck +- right-click to run action +if a unauthorized player destroys a block a sign is on, is canceled +fixed column check for table updating +updated for 670 +configuration nodes are moved to more organized parent nodes (easier to edit if using an intelligent text editor) +- older nodes are still supported (for now) + + +Version 1.6.3.6 - 4/6/11 - jascotty2 +again, previously forgot to update the date in last version +- added additional checks to prevent future false-positives +-- (also checks if comment on download matches plugin, not if plugin >= comment version) +figured out the integrity check errors :) +now runs checks on changed tables to check if needs updating (and adds columns as necessary) +- just transaction log table for now (only table with added columns) +another option added: AutoUpdate +- if a new update is found, will download & install +- can also run manually with /shop update (OP-only, since forces update & restarts server plugins) + + +Version 1.6.3.5 - 4/4/11 - jascotty2 +fixed a rare null exception in integretycheck? +fixed saving errors when transaction logging disabled +implemented infinite stock (setting stock to -1 makes infinite) +# 1,000+ downloads in one day! + + +Version 1.6.3.4 - 4/3/11 - jascotty2 +fixed a rare invalid function signature error +fixed if a player tries to buy/sell air +OPs now completely bypass security checks +more changes to error reporting format: added a custom message +# 10,000+ lines of code :) + + +Version 1.6.3.3 - 4/2/11 - jascotty2 +renamed monstertamer package, to avoid conflicts if you're using the monstertamer plugin +should have a reported bug in integrety check taken care of.. not sure of the cause, though + + +Version 1.6.3.2 - 4/2/11 - jascotty2 +fixed more error reporting bugs (still can't find one stupid stock flatfile bug) +added stock messages to config + + +Version 1.6.3.1 - 4/1/11 - jascotty2 +fixed chickens & buying more than one animal +updated error reporting(again) +rand libary update for my version of CookieMonster + + +Version 1.6.3 - 4/1/11 - jascotty2 +fixed buy/sell == 0 ? no (in shoplist, 0 incorrectly showed as no) +updated for 612 (and 600+ .. had accidently been using the wrong libary) +LivingEntities can now be purchased! :D +- special thanks to fullwall for the MonsterTamer plugin, from which this ability comes from (1.3) +- if capable, will attack the anything attacking the purchaser (if nearby) +-- todo? make them follow the purchaser around? + + +Version 1.6.2.8 - 3/31/11 - jascotty2 +nullpointer fixed if running 602 with old groupmanager +other misc. null fixes +removed redundant reload points for iConomy +repaired a mysql totalTransactionLog format error +error reporting now begins with a portion of the date, so i can see them listed in order +more info added to error reports.. still can't figure out the stock flatfile save bug + + +Version 1.6.2.7 - 3/30/11 - jascotty2 +improved error reporting format & removed a few unnecessary send triggers + + +Version 1.6.2.6 - 3/30/11 - jascotty2 +now checks for missing & unused configuration nodes +strings have default values if missing +auto error reporting added (can be disabled) +now compatible with bukkit 602 +help main page integration can be disabled +hidden changes to get one step closer to dynamic pricing + + +Version 1.6.2.5 - 3/28/11 - jascotty2 +forgot to update the date in the last release, so a false 'newer release' was showing up +fixed help support when help loaded before bettershop + + +Version 1.6.2.4 - 3/25/11 - jascotty2 +Added support for my new plugin: MinecraftIM :) +removed a stock debugging line i forgot to remove +manually set locale for updating date format.. should fix non-us countries? + + +Version 1.6.2.3 - 3/23/11 - jascotty2 +added stock db to reload +fixed a stock update on sell +updated plugin commands to include sellstack + + +Version 1.6.2.2 - 3/23/11 - jascotty2 +more commands added to register with help +fixed a MySQL update syntax error for stock + + +Version 1.6.2.1 - 3/23/11 - jascotty2 +fixed a plural search bug +added /sellstack (much like buystack) +fixed a buyagain logic error +/shop import - insert values from file in plugin folder +/shop restore - clear shop & load from saved backup file +flatfile itemstock repaired + + +Version 1.6.2 - 3/23/11 - jascotty2 +stock option - items can have a finite amount + - selling to the shop increases the stock, buying decreases + - can have a minimum update interval: time before will reset stock to initial amounts + - maximum stock: most will hold + - noOverStock: will not allow selling if item stock is full +- note on stock: does not track each item's damage values (if tools) .'. selling a damaged good allows 1 intact to be bought +/shop restock (permission BetterShop.admin.restock) to update how much stock the shop has +permission node BetterShop.admin.backup added for backing up (and eventually restoring) the pricelist +added Help plugin integration: will automatically register commands with help +added some more MySQL db connection reconnect points (autoReconnect=true not working is becoming irritating) +more checking for iConomy account mod failures (i've had more problems with it) +fixed a format error if using a replacement char (like ) +restructured sellall inventory editing +# passed 7500 lines of code! (430KB) + + +Version 1.6.1.4 - 3/21/11 - jascotty2 +(hopefully) fixed MySQL region formatting errors (such as europe where the decimal is a comma) +addto/updated help menu +added [command] to help: lookup a specific command (also accepts aliases) +fixed flatfile total transactions log +shop ver[sion] also shows if the plugin is up-to-date + + +Version 1.6.1.3 - 3/20/11 - jascotty2 +default color for items +plural-insensitive items: if not found, will check if plural & remove "s" +fixed transaction log insert missing the price +was having problems reading bit from php, so changed SOLD column from bit to tinyint +- alter table BetterShopMarketActivity change column SOLD SOLD TINYINT; +finished flatfile support for transaction logging + + +Version 1.6.1.2 - 3/20/11 - jascotty2 +runs MySQL connection check before executing database queries, and manually attempts reload if not connected +- (still trying to iron out MySQL connection bugs) +removed list debugging lines i forgot to remove earlier + + +Version 1.6.1.1 - 3/19/11 - jascotty2 +removed double negative add check.. can add an item for -1, -1 +fixed some Item errors: color and .equals(ItemStack).. fixes itemdb colors and sellall +fixed page numbering for when there are hidden items +fixed sell for 0: a <= instead of < .. thought i'd fixed that, but apparently not all of those. + + +Version 1.6.1 - 3/19/11 - jascotty2 +update check is optional (in config) +/shop backup to save pricelist to a backup csv file +updates itemsdb on shopload, in case the admin wanted to update colors/kits and such +added price column in marketHistory table +- upgrading from older version? +- ALTER TABLE BetterShopMarketActivity ADD COLUMN PRICE DECIMAL(11,2); +added flatfile support for transaction logging (uses csv) +removed pricelist save file on close (is already saved on edit) +if an item is marked as no buy, no sell, don't show on shoplist +itemsdb names can be saved (and outputted) with title formatting (eg RedstoneTorch) +- included itemsdb also expanded & names formatted +case-insensitive item matching + + +Version 1.6.1 alpha - 3/18/11 - jascotty2 +Checks git to see if there is a newer version available (todo: config & download update?) +fixed price = 0 to be free +fixed null in sellall if selling all sellable + + +Version 1.6.0f - 3/18/11 - jascotty2 +better error check for config & importing old yml pricelist +MySQL pricelist can be cached for a given timespan (decreases table selects) +fixed sell bug (selling one would credit for amount in the first stack) + + +Version 1.6.0e - 3/17/11 - jascotty2 +fixed a removal error - there was a typecast exception previously + + +Version 1.6.0d - 3/17/11 - jascotty2 +if the old PriceList.yml file exists, will import into whatever db is enabled, then rename the file +fixed public message bug - would send broadcast (num players-1) times + + +Version 1.6.0c (?) - 3/17/11 - jascotty2 +added more options: allowbuyillegal, usemaxstack, buybacktools +fixed buystack history +major buystack logic error repaired - previously didn't work correctly +buystack can now be given multiple items, or number of stacks: "/buystack wool 5" or "/buystack wool blackdye reddye" +damaged tools can now be resold for an adjusted value of sellprice*(1-(damage/maxdamage)) + + +Version 1.6.0b (?) - 3/16/11 - jascotty2 +fixed a minor buy error if tried to buy more than they can hold, and could hold 0 more +major MySQL problem fixed - pricelist table wasn't being created + + +Version 1.6.0 - 3/16/11 - jascotty2 +major code restructure & reorganization (technically a 'version 2') +changed item db to a more descriptive yml file +- can define what items make an item (for calculated/dynamic costs) +- can change what color it will be outputted as in the listing +- define kits (sold.. store won't buy them) +MySQL buy & sell fields increased to decimal(11,2), and cap in program set to 999,999,999 +shoplist can now optionally not show listing tail (or head, but recommended to leave of ) +removed value check - can now "sell" items that sell for 0 +added /shop command alias - for if a user is more used to "/shop help" than "/shophelp" +many commands have sub-aliases.. eg. shoplist kits will run shoplistkits +now does pre-check for if user can hold as much as they're trying to buy +- and "all" to try to buy as many as they can hold +items can be marked as illegal, and can only be purchased by a select few (using permissions) +- also not shown to users who cannot buy them, except with pricecheck +option in config to make transactions publicly broadcasted +option in config to have a custom sort order +shopsellall can be given multiple items (/sell all cobble gravel flint dirt) +now shows what items were sold when using shopsellall +fixed bug where single sell parameter would == sell [item] all +all, full, -1 added as a pricelist pagenumber: will print the full list (intended for console use) +marketactivity table truncate logic repaired.. +marketactivity MySQL table changed with primary key(DATE, USER, ID) (instead of DATE, USER) to resolve conflits with sellall +items can be marked as 'legal: false', and only those with permissions can buy them (can still sell) +if encounters errors while editing a player's account, will attempt to reload iConomy +sellall can be given "inv" as first parameter to only sell from inventory (not in quick access slots) +shopcheck given partial name matching: "/shopcheck wool" (not "shopcheck 35") will show all colored wool prices :) +fixed sellall bug when an item not for sale +/shopbuystack (buystack): buy a stack of an item (usually 64) +/shopbuyagain (buyagain,sba): repeat last buy action +/shopsellagain (sellagain,ssa): repeat last sell action +/shopkits (skits): show a listing of avalliable kits (will be elaborated later) +can now buy kits :) +/add now has sell as an optional parameter.. will set to -1 if not given (or if is a kit) + + +Version 1.5.9b - 3/11/11 - jascotty2 +width outputting in minecraft chat (for /shoplist): now can have item (or listing) right-align, left-align, or centered +implemented transaction log and total transactions record +no longer dependent on permissions: if missing, ops have .admin.* & everyone has .user.* +bugs: MySQL buy & sell is decimal(6,2), limiting price to 9,999 (plus a nondescriptive error) + - to fix (db minecraft, table BetterShop): + alter table minecraft.BetterShop change column buy buy decimal(11,2); + alter table minecraft.BetterShop change column sell sell decimal(11,2); + unkown error: MySQL connection seems to get dropped & won't connect until restart plugin + seems to stem from iConomy (v4.4 (Arcadia)) + shopload should fix this, but iconomy will still be down.. if you have a plugin like hotswap, reload iConomy + not a bug, but forgot to remove: logs select statements being executed + +Version 1.5.8(?) - 3/10/11 - jascotty2 +fixed a sell error for one parameter +major-ish code restructure - removed data redundancy +/ shopsell all [item] (alias to /shopsell [item] all) + + +Version 1.5.7(?) - 3/10/11 - jascotty2 +/ shopsell [item] all added: sell all of an item that you have +fixed total sell value min to .01 instead of 1 (if your server uses low prices) + before this fix, one could get around this check by trying to sell alot of an item +now automatically downloads mysql dependency (zip from mysql mirror & unzip or from iConomy mirror) + + +Version 1.5.5(?) - 3/9/11 - jascotty2 +Pricelist can now be in a MySQL database + + +Version 1.5.4 - 3/2/11 +Fixed some lingering bugs with lologarithm's help. + + +Version 1.5.3 +Fixed sell 0 < sellprice < 1 bug. + + +Version 1.5.2 +Now it'll convert your old pricelist.yml to the new 1.5.x format automagically! + + +Version 1.5.1 +Fixed the bug where setting a price to -1 didn't disable it +Fixed the "LOLWTFDOESN'TWORK" bug. +Made price-list listing configurable in config.yml. The way it was 'sposta be. + +(older versions not shown..) + diff --git a/dist/BetterShop.jar b/dist/BetterShop.jar new file mode 100644 index 0000000..e42c5ef Binary files /dev/null and b/dist/BetterShop.jar differ diff --git a/manifest.mf b/manifest.mf new file mode 100644 index 0000000..cafa1cd --- /dev/null +++ b/manifest.mf @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Class-Path: ../lib/mysql.jar ../lib/mysql-connector-java-bin.jar ../lib/mysql-connector-java-5.1.14-bin.jar ../lib/mysql-connector-java-5.1.15-bin.jar diff --git a/nukem2000_pricelist_generator.xlsx b/nukem2000_pricelist_generator.xlsx new file mode 100644 index 0000000..ed84e48 Binary files /dev/null and b/nukem2000_pricelist_generator.xlsx differ diff --git a/src/com/bukkit/jjfs85/BetterShop/BSPriceList.java b/src/com/bukkit/jjfs85/BetterShop/BSPriceList.java deleted file mode 100644 index 0296592..0000000 --- a/src/com/bukkit/jjfs85/BetterShop/BSPriceList.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.bukkit.jjfs85.BetterShop; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.*; -import java.util.logging.Logger; - -import org.bukkit.Material; -import org.bukkit.material.MaterialData; -import org.bukkit.util.config.*; - -public class BSPriceList { - private final static Logger logger = Logger.getLogger("Minecraft"); - final Map BuyMap = new HashMap(); - final Map SellMap = new HashMap(); - final Map NameMap = new HashMap(); - final List keys = new LinkedList(); - private File PLfile; - private Configuration PriceList; - - public void load(File PLpath, String fileName) throws IOException { - int i = 0; - PLfile = new File(PLpath, fileName); - if (!PLfile.getParentFile().exists()) { - logger.info("Creating " + PLpath.getAbsolutePath()); - PLpath.mkdirs(); - } - if (!PLfile.exists()) { - logger.info("Creating " + PLfile.getAbsolutePath()); - PLfile.createNewFile(); - } - logger.info("Loading PriceList.yml"); - PriceList = new Configuration(PLfile); - PriceList.load(); - logger.info("PriceList.yml loaded."); - BuyMap.clear(); - SellMap.clear(); - NameMap.clear(); - - try { - keys.addAll(PriceList.getKeys("prices")); - } catch (Exception e0) { - logger.info("Empty PriceList"); - return; - } - while (i < keys.size()) { - int buy = -1; - int sell = -1; - String name = "Unk"; - String[] split = keys.get(i).split("[^0-9]"); - if (split.length != 0) { - int id = Integer.parseInt(split[split.length-1]); - if (keys.contains("item" + String.valueOf(id))) { - buy = PriceList.getInt("prices.item" + String.valueOf(id) - + ".buy", -1); - sell = PriceList.getInt("prices.item" + String.valueOf(id) - + ".sell", -1); - name = PriceList.getString("prices.item" - + String.valueOf(id) + ".name", "Unk"); - } - if ((buy != -1) && (sell != -1)) { - BuyMap.put(id, buy); - SellMap.put(id, sell); - NameMap.put(id, name); - } - } - i++; - } - } - - public boolean isForSale(String s) throws Exception { - int i; - i = itemDb.get(s).getItemTypeId(); - return isForSale(i); - } - - public boolean isForSale(int i) { - return NameMap.containsKey(i); - } - - public int getBuyPrice(String s) throws Exception { - int i; - i = itemDb.get(s).getItemTypeId(); - return getBuyPrice(i); - } - - public int getBuyPrice(int i) throws Exception { - if (NameMap.containsKey(i)) { - return BuyMap.get(i); - } else - throw new Exception(); - } - - public int getSellPrice(String s) throws Exception { - int i; - i = itemDb.get(s).getItemTypeId(); - return getSellPrice(i); - } - - public int getSellPrice(int i) throws Exception { - if (NameMap.containsKey(i)) { - return SellMap.get(i); - } else - throw new Exception(); - } - - public void setPrice(String item, String b, String s) throws Exception { - MaterialData mat = null; - mat = itemDb.get(item); // check if it's in items.db - int i = mat.getItemTypeId(); - if ((Integer.parseInt(b) >= 0) && (Integer.parseInt(s) >= 0)) { - if (NameMap.containsKey(i)) { - BuyMap.remove(i); - SellMap.remove(i); - NameMap.remove(i); - } - BuyMap.put(i, Integer.parseInt(b)); - SellMap.put(i, Integer.parseInt(s)); - NameMap.put(i, itemDb.getName(i, mat.getData())); - } - save(); - } - - public void remove(String s) throws Exception { - MaterialData matdat = itemDb.get(s); - Material mat = matdat.getItemType(); - if (NameMap.containsKey(mat.getId())) { - BuyMap.remove(mat.getId()); - SellMap.remove(mat.getId()); - NameMap.remove(mat.getId()); - } - save(); - } - - private void save() { - BufferedWriter output = null; - try { - output = new BufferedWriter(new FileWriter(PLfile)); - } catch (IOException e1) { - logger.warning("Cannot write to " + PLfile.getName()); - e1.printStackTrace(); - } - try { - // FileWriter always assumes default encoding is OK! - output.write("prices:"); - output.newLine(); - for (int i = 0; i < 2280; i++) { - if (BuyMap.containsKey(i)) { - output.write(" item" + String.valueOf(i) + ":"); - output.newLine(); - output.write(" name: " + NameMap.get(i).toLowerCase()); - output.newLine(); - output.write(" buy: " + BuyMap.get(i)); - output.newLine(); - output.write(" sell: " + SellMap.get(i)); - output.newLine(); - } - } - } catch (Exception e) { - logger.warning("Cannot write to " + PLfile.getName()); - e.printStackTrace(); - } finally { - try { - output.flush(); - output.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - } -} diff --git a/src/com/bukkit/jjfs85/BetterShop/BetterShop.java b/src/com/bukkit/jjfs85/BetterShop/BetterShop.java deleted file mode 100644 index 7929360..0000000 --- a/src/com/bukkit/jjfs85/BetterShop/BetterShop.java +++ /dev/null @@ -1,499 +0,0 @@ -package com.bukkit.jjfs85.BetterShop; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.logging.Logger; - -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.material.MaterialData; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.PluginLoader; -import org.bukkit.plugin.java.JavaPlugin; - -import com.nijiko.Messaging; -import com.nijiko.permissions.PermissionHandler; -import com.nijikokun.bukkit.Permissions.Permissions; -import com.nijikokun.bukkit.iConomy.iConomy; - -/** - * BetterShop for Bukkit - * - * @author jjfs85 - */ -public class BetterShop extends JavaPlugin { - private final static Logger logger = Logger.getLogger("Minecraft"); - private final static String commandPrefix = ""; - private final static String messagePrefix = "§c[§7SHOP§c] "; - private static final String name = "BetterShop"; - private final HashMap leftover = new HashMap(); - private final BSPriceList PriceList = new BSPriceList(); - private static PermissionHandler Permissions = null; - private final static File pluginFolder = new File("plugins", name); - - public BetterShop(PluginLoader pluginLoader, Server instance, - PluginDescriptionFile desc, File folder, File plugin, - ClassLoader cLoader) throws IOException { - super(pluginLoader, instance, desc, folder, plugin, cLoader); - - // NOTE: Event registration should be done in onEnable not here as all - // events are unregistered when a plugin is disabled - } - - public void onEnable() { - - PluginDescriptionFile pdfFile = this.getDescription(); - logger.info("Loading " + pdfFile.getName() + " version " - + pdfFile.getVersion() + "..."); - - // Load pricelist.yml - try { - PriceList.load(pluginFolder, "PriceList.yml"); - } catch (IOException e) { - e.printStackTrace(); - logger.warning("cannot load PriceList.yml"); - this.setEnabled(false); - } - - // ready items.db - try { - itemDb.load(pluginFolder, "items.db"); - } catch (IOException e) { - logger.warning("cannot load items.db"); - e.printStackTrace(); - this.setEnabled(false); - } - - // setup the permissions - setupPermissions(); - - // Just output some info so we can check - // all is well - logger.info(pdfFile.getName() + " version " + pdfFile.getVersion() - + " is enabled!"); - } - - public void onDisable() { - - // NOTE: All registered events are automatically unregistered when a - // plugin is disabled - - // EXAMPLE: Custom code, here we just output some info so we can check - // all is well - logger.info("BetterShop now unloaded"); - } - - public void setupPermissions() { - Plugin test = this.getServer().getPluginManager().getPlugin( - "Permissions"); - - if (BetterShop.Permissions == null) { - if (test != null) { - BetterShop.Permissions = ((Permissions) test).getHandler(); - } else { - logger.info(Messaging.bracketize(name) - + " Permission system not enabled. Disabling plugin."); - this.getServer().getPluginManager().disablePlugin(this); - } - } - } - - @Override - public boolean onCommand(CommandSender sender, Command command, - String commandLabel, String[] args) { - String[] trimmedArgs = args; - String commandName = command.getName().toLowerCase(); - - try { - logger.info(((Player) sender).getName() + " used command " - + command.getName()); - } catch (Exception e) { - } - - if (commandName.equals("shoplist")) { - return list(sender, trimmedArgs); - } else if (commandName.equals("shophelp")) { - return help(sender); - } else if (commandName.equals("shopbuy")) { - return buy(sender, trimmedArgs); - } else if (commandName.equals("shopsell")) { - return sell(sender, trimmedArgs); - } else if (commandName.equals("shopadd")) { - return add(sender, trimmedArgs); - } else if (commandName.equals("shopremove")) { - return remove(sender, trimmedArgs); - } else if (commandName.equals("shopload")) { - return load(sender); - } else if (commandName.equals("shopcheck")) { - return check(sender, trimmedArgs); - } - return false; - } - - public boolean check(CommandSender player, String[] s) { - MaterialData mat; - if (!hasPermission(player, "BetterShop.user.check")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - if (s.length != 1) { - return false; - } - try { - mat = itemDb.get(s[0]); - } catch (Exception e) { - sendMessage(player, "What is " + s[0] + "?"); - return true; - } - try { - sendMessage(player, String.format("[%s] Buy: %d Sell: %d", itemDb - .getName(mat.getItemTypeId(), mat.getData()), PriceList - .getBuyPrice(mat.getItemTypeId()), PriceList - .getSellPrice(mat.getItemTypeId()))); - } catch (Exception e) { - sendMessage(player, "That's not on the market."); - return true; - } - return true; - } - - public boolean list(CommandSender player, String[] s) { - int pagesize = 9; - int page = 0; - if (!hasPermission(player, "BetterShop.user.list")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - try { - page = (s.length == 0) ? 1 : Integer.parseInt(s[0]); - } catch (Exception e) { - sendMessage(player, "That's not a page number, idiot."); - return false; - } - int pages = (int) Math.ceil((double) PriceList.NameMap.size() - / pagesize); - if ((s.length != 0) && (s.length != 1)) { - return false; - } else if (page > pages) { - sendMessage(player, "There is no page " + page + "."); - return true; - } else { - int linenum = 1; - int i = 1; - sendMessage(player, String.format( - "---- Price-list Page: %2d of %2d ----", page, pages)); - while ((linenum < page * pagesize) && (i < 2280)) { - try { - itemDb.get(i, (byte) 0); - } catch (Exception e1) { - i++; - continue; - } - if (PriceList.isForSale(i)) { - if (linenum > (page - 1) * pagesize) { - try { - sendMessage(player, "[" - + itemDb.getName(i, (byte) 0) + "] Buy: " - + PriceList.getBuyPrice(i) + " Sell: " - + PriceList.getSellPrice(i)); - } catch (Exception e) { - e.printStackTrace(); - logger.warning("Pricelist error!"); - } - } - linenum++; - } - i++; - } - sendMessage(player, "-----------------------------"); - return true; - } - } - - public boolean buy(CommandSender player, String[] s) { - MaterialData item = new MaterialData(0); - int price = 0; - int amtleft = 0; - int amtbought = 1; - int cost = 0; - if (!hasPermission(player, "BetterShop.user.buy")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - if ((s.length > 2) || (s.length == 0)) { - sendMessage(player, "What?"); - return false; - } else if (anonymousCheck(player)) { - return true; - } else { - try { - item = itemDb.get(s[0]); - } catch (Exception e2) { - sendMessage(player, "What is " + s[0] + "?"); - } - try { - price = PriceList.getBuyPrice(item.getItemTypeId()); - } catch (Exception e1) { - sendMessage(player, "That's not for sale!"); - return true; - } - if (s.length == 2) - try { - amtbought = Integer.parseInt(s[1]); - } catch (Exception e) { - return false; - } - if (amtbought < 0) { - sendMessage(player, "...Nice try."); - return true; - } - cost = amtbought * price; - try { - if (debit(player, cost)) { - sendMessage(player, "For " - + amtbought - + " " - + itemDb.getName(item.getItemTypeId(), item - .getData()) + "? " + cost + " " - + iConomy.currency + ". Thank you, come again!"); - leftover.clear(); - ItemStack itemS = new ItemStack(item.getItemTypeId(), - amtbought, (short) 0, item.getData()); - leftover.putAll(((Player) player).getInventory().addItem( - itemS)); - amtleft = (leftover.size() == 0) ? 0 : (leftover.get(0)) - .getAmount(); - if (amtleft > 0) { - cost = amtleft * price; - sendMessage(player, - "You didn't have enough space for all that."); - sendMessage(player, "I'm refundng " + cost + " " - + iConomy.currency - + " for what you couldn't hold."); - credit(player, cost); - } - return true; - } else { - sendMessage(player, "Not enough money."); - return true; - } - } catch (Exception e) { - return false; - } - } - } - - public boolean sell(CommandSender player, String[] s) { - int amtSold = 1; - MaterialData item = new MaterialData(0); - if (!hasPermission(player, "BetterShop.user.sell")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - if ((s.length > 2) || (s.length == 0)) { - return false; - } else if (anonymousCheck(player)) { - return true; - } else { - try { - item = itemDb.get(s[0]); - } catch (Exception e1) { - sendMessage(player, "What's " + s[0] + "?"); - return false; - } - ItemStack itemsToSell = item.toItemStack(); - if (s.length == 2) - try { - amtSold = Integer.parseInt(s[1]); - } catch (Exception e) { - sendMessage(player, s[1] + " is definitely not a number."); - } - if (amtSold < 0) { - sendMessage(player, - "Why would you want to buy at the selling price!?"); - return true; - } - itemsToSell.setAmount(amtSold); - PlayerInventory inv = ((Player) player).getInventory(); - leftover.clear(); - leftover.putAll(inv.removeItem(itemsToSell)); - if (leftover.size() > 0) { - amtSold = amtSold - leftover.get(0).getAmount(); - sendMessage(player, "You only had " + amtSold + "."); - } - try { - credit(player, amtSold - * PriceList.getSellPrice(item.getItemTypeId())); - sendMessage(player, "You sold " + amtSold + " " - + itemDb.getName(item.getItemTypeId(), item.getData()) - + " at " + PriceList.getSellPrice(item.getItemTypeId()) - + " each for a total of " + amtSold - * PriceList.getSellPrice(item.getItemTypeId())); - } catch (Exception e) { - e.printStackTrace(); - } - return true; - } - } - - public boolean add(CommandSender player, String[] s) { - if (s.length != 3) { - return false; - } - MaterialData mat = new MaterialData(0); - try { - mat = itemDb.get(s[0]); - } catch (Exception e1) { - sendMessage(player, "What's " + s[0] + "?"); - return false; - } - if (!hasPermission(player, "BetterShop.admin.add")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - try { - PriceList.setPrice(s[0], s[1], s[2]); - } catch (Exception e) { - sendMessage(player, "Something wasn't right there."); - return false; - } - try { - sendMessage(player, String.format( - "[%s] added at the prices: Buy: %d Sell: %d", itemDb - .getName(mat.getItemTypeId(), mat.getData()), - PriceList.getBuyPrice(mat.getItemTypeId()), PriceList - .getSellPrice(mat.getItemTypeId()))); - } catch (Exception e) { - e.printStackTrace(); - } - return true; - } - - public boolean remove(CommandSender player, String[] s) { - if ((!hasPermission(player, "BetterShop.admin.remove"))) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - if (s.length != 1) { - return false; - } else { - try { - PriceList.remove(s[0]); - sendMessage(player, s[0] + " has been removed from the shop."); - } catch (Exception e) { - sendMessage(player, "You done goofed. Check the name."); - } - return true; - } - } - - public boolean load(CommandSender player) { - if (!hasPermission(player, "BetterShop.admin.load")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - try { - PriceList.load(pluginFolder, "PriceList.yml"); - } catch (IOException e) { - e.printStackTrace(); - sendMessage(player, "Pricelist load error. See console."); - return true; - } - sendMessage(player, "PriceList loaded."); - return true; - } - - public boolean help(CommandSender player) { - if (!hasPermission(player, "BetterShop.user.help")) { - sendMessage(player, "OI! You don't have permission to do that!"); - return true; - } - sendMessage(player, "--------- Better Shop Usage --------"); - sendMessage(player, "/" + commandPrefix - + "shoplist - List shop prices"); - sendMessage(player, "/" + commandPrefix - + "shopbuy [item] - Buy items"); - sendMessage(player, "/" + commandPrefix - + "shopsell [item] - Sell items"); - sendMessage(player, "/" + commandPrefix - + "shopcheck [item] - Check prices of item"); - if (BetterShop.hasPermission(player, "BetterShop.admin")) { - sendMessage(player, "**-------- Admin commands --------**"); - sendMessage(player, "/" + commandPrefix - + "shopadd [item] [$buy] [$sell] - Add an item to the shop"); - sendMessage(player, "/" + commandPrefix - + "shopremove [item] - Remove an item from the shop"); - sendMessage(player, "/" + commandPrefix - + "shopload - Reload the PriceList.yml file"); - sendMessage(player, "----------------------------------"); - } - return true; - } - - private static boolean hasPermission(CommandSender player, String string) { - try { - if (BetterShop.Permissions.has((Player) player, string)) { - return true; - } - return false; - } catch (Exception e) { - return true; - } - } - - private boolean anonymousCheck(CommandSender sender) { - if (!(sender instanceof Player)) { - sendMessage(sender, - "Cannot execute that command, I don't know who you are!"); - return true; - } else { - return false; - } - } - - private final static void sendMessage(CommandSender player, String s) { - player.sendMessage(messagePrefix + s); - } - - private final boolean debit(CommandSender player, int amount) - throws Exception { - int balance = 0; - Object db = BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.iConomy").getField("db") - .get(null); - balance = (Integer) BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.Database").getMethod( - "get_balance", String.class).invoke(db, - ((Player) player).getName()); - if (balance < amount) - return false; - BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.Database").getMethod( - "set_balance", String.class, Integer.TYPE).invoke(db, - ((Player) player).getName(), balance - amount); - return true; - } - - private final boolean credit(CommandSender player, int amount) - throws Exception { - int balance = 0; - Object db = BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.iConomy").getField("db") - .get(null); - balance = (Integer) BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.Database").getMethod( - "get_balance", String.class).invoke(db, - ((Player) player).getName()); - BetterShop.class.getClassLoader().loadClass( - "com.nijikokun.bukkit.iConomy.Database").getMethod( - "set_balance", String.class, Integer.TYPE).invoke(db, - ((Player) player).getName(), balance + amount); - return true; - } -} diff --git a/src/com/bukkit/jjfs85/BetterShop/itemDb.java b/src/com/bukkit/jjfs85/BetterShop/itemDb.java deleted file mode 100644 index 530afca..0000000 --- a/src/com/bukkit/jjfs85/BetterShop/itemDb.java +++ /dev/null @@ -1,132 +0,0 @@ -/** This code was literally yoinked from the Essentials plugin source - * All credit for this work goes to Zenexer, ementalo, Eris, - * and/or Eggroll. Hope you guys don't mind that I'm using it, but hey, - * why reinvent the wheel, right? - * - * Ok, now I've had to edit it to add subtype support for my plugin. ~jjfs85 - */ - -package com.bukkit.jjfs85.BetterShop; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -import org.bukkit.material.MaterialData; - -public class itemDb { - private final static Logger logger = Logger.getLogger("Minecraft"); - private static Map map = new HashMap(); - private static Map submap = new HashMap(); - - public static void load(File folder, String fname) throws IOException { - folder.mkdirs(); - File file = new File(folder, fname); - if (!file.exists()) { - file.createNewFile(); - InputStream res = itemDb.class.getResourceAsStream("/items.db"); - FileWriter tx = new FileWriter(file); - try { - for (int i = 0; (i = res.read()) > 0;) - tx.write(i); - } finally { - tx.flush(); - tx.close(); - res.close(); - } - } - - BufferedReader rx = new BufferedReader(new FileReader(file)); - try { - map.clear(); - for (int i = 0; rx.ready(); i++) { - try { - String line = rx.readLine().trim().toLowerCase(); - if (line.startsWith("#")) - continue; - String[] parts = line.split("[^a-z0-9]"); - if (parts.length < 2) - continue; - int numeric = Integer.parseInt(parts[1]); - map.put(parts[0], numeric); - if (parts.length == 3) { - numeric = Integer.parseInt(parts[2]); - submap.put(parts[0], ((Integer) numeric).byteValue()); - } - } catch (Exception ex) { - logger.warning("Error parsing " + fname + " on line " + i); - } - } - } finally { - rx.close(); - } - } - - public static MaterialData get(int i, byte b) throws Exception { - return get(String.format("%d:%d", i, b)); - } - - public static MaterialData get(String s) throws Exception { - MaterialData retval = new MaterialData(0); - String[] split = s.split(":"); - int id = 0; - byte data = 0; - try { - id = Integer.parseInt(split[0]); - try { - data = Byte.parseByte(split[1], 10); - } catch (Exception e1) { - data = 0; - } - if (map.containsValue(id)) - retval = new MaterialData(id, data); - else - throw new Exception("No data allowed"); - } catch (Exception e2) { - if (map.containsKey(s)) { - // if it's a valid name - id = map.get(s); - retval = new MaterialData(id); - if (submap.containsKey(s)) { - retval.setData(submap.get(s)); - } - } else - throw new Exception("Unknown material"); - } - return retval; - } - - public static String getName(int i, byte d) throws Exception { - File file = new File("plugins/BetterShop", "items.db"); - BufferedReader items = new BufferedReader(new FileReader(file)); - String line = items.readLine(); - while (line != null) { - if (line.charAt(0) == '#') { - line = items.readLine(); - continue; - } - if (Integer.parseInt(line.split("[^a-z0-9]")[1]) == i) { - while ((line != null) && (d != 0)) { - if (line.split("[^a-z0-9]").length == 3) { - if (Byte.parseByte(line.split("[^a-z0-9]")[2], 10) == d) { - return line.split("[^a-z0-9]")[0]; - } else { - line = items.readLine(); - } - } else { - line = items.readLine(); - } - } - return line.split("[^a-z0-9]")[0]; - } - line = items.readLine(); - } - throw new Exception(String.format("EOF: no item # %d-%d", i, d)); - } -} \ No newline at end of file diff --git a/src/com/nijikokun/register_1_5/payment/Method.java b/src/com/nijikokun/register_1_5/payment/Method.java new file mode 100644 index 0000000..7b77179 --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/Method.java @@ -0,0 +1,183 @@ +package com.nijikokun.register_1_5.payment; + +import org.bukkit.plugin.Plugin; + +/** + * Interface to be implemented by a payment method. + * + * @author Nijikokun (@nijikokun) + * @copyright Copyright (C) 2011 + * @license AOL license + */ +public interface Method { + /** + * Encodes the Plugin into an Object disguised as the Plugin. + * If you want the original Plugin Class you must cast it to the correct + * Plugin, to do so you have to verify the name and or version then cast. + * + *
+     *  if(method.getName().equalsIgnoreCase("iConomy"))
+     *   iConomy plugin = ((iConomy)method.getPlugin());
+ * + * @return Object + * @see #getName() + * @see #getVersion() + */ + public Object getPlugin(); + + /** + * Returns the actual name of this method. + * + * @return String Plugin name. + */ + public String getName(); + + /** + * Returns the actual version of this method. + * + * @return String Plugin version. + */ + public String getVersion(); + + /** + * Returns the amount of decimal places that get stored + * NOTE: it will return -1 if there is no rounding + * + * @return int for each decimal place + */ + public int fractionalDigits(); + + /** + * Formats amounts into this payment methods style of currency display. + * + * @param amount Double + * @return String - Formatted Currency Display. + */ + public String format(double amount); + + /** + * Allows the verification of bank API existence in this payment method. + * + * @return boolean + */ + public boolean hasBanks(); + + /** + * Determines the existence of a bank via name. + * + * @param bank Bank name + * @return boolean + * @see #hasBanks + */ + public boolean hasBank(String bank); + + /** + * Determines the existence of an account via name. + * + * @param name Account name + * @return boolean + */ + public boolean hasAccount(String name); + + /** + * Check to see if an account name is tied to a bank. + * + * @param bank Bank name + * @param name Account name + * @return boolean + */ + public boolean hasBankAccount(String bank, String name); + + /** + * Forces an account creation + * + * @param name Account name + * @return boolean + */ + public boolean createAccount(String name); + + /** + * Forces an account creation + * + * @param name Account name + * @param balance Initial account balance + * @return boolean + */ + public boolean createAccount(String name, double balance); + + /** + * Returns a MethodAccount class for an account name. + * + * @param name Account name + * @return MethodAccount or Null + */ + public MethodAccount getAccount(String name); + + + /** + * Returns a MethodBankAccount class for an account name. + * + * @param bank Bank name + * @param name Account name + * @return MethodBankAccount or Null + */ + public MethodBankAccount getBankAccount(String bank, String name); + + /** + * Checks to verify the compatibility between this Method and a plugin. + * Internal usage only, for the most part. + * + * @param plugin Plugin + * @return boolean + */ + public boolean isCompatible(Plugin plugin); + + /** + * Set Plugin data. + * + * @param plugin Plugin + */ + public void setPlugin(Plugin plugin); + + /** + * Contains Calculator and Balance functions for Accounts. + */ + public interface MethodAccount { + public double balance(); + public boolean set(double amount); + public boolean add(double amount); + public boolean subtract(double amount); + public boolean multiply(double amount); + public boolean divide(double amount); + public boolean hasEnough(double amount); + public boolean hasOver(double amount); + public boolean hasUnder(double amount); + public boolean isNegative(); + public boolean remove(); + + @Override + public String toString(); + } + + /** + * Contains Calculator and Balance functions for Bank Accounts. + */ + public interface MethodBankAccount { + public double balance(); + public String getBankName(); + public int getBankId(); + public boolean set(double amount); + public boolean add(double amount); + public boolean subtract(double amount); + public boolean multiply(double amount); + public boolean divide(double amount); + public boolean hasEnough(double amount); + public boolean hasOver(double amount); + public boolean hasUnder(double amount); + public boolean isNegative(); + public boolean remove(); + + @Override + public String toString(); + } +} diff --git a/src/com/nijikokun/register_1_5/payment/Methods.java b/src/com/nijikokun/register_1_5/payment/Methods.java new file mode 100644 index 0000000..3943d2c --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/Methods.java @@ -0,0 +1,237 @@ +package com.nijikokun.register_1_5.payment; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +import java.util.HashSet; +import java.util.Set; + +/** + * The Methods initializes Methods that utilize the Method interface + * based on a "first come, first served" basis. + *

+ * Allowing you to check whether a payment method exists or not. + *

+ * Methods also allows you to set a preferred method of payment before it captures + * payment plugins in the initialization process. + *

+ * in bukkit.yml: + *

+ *  economy:
+ *      preferred: "iConomy"
+ * 
+ * + * @author: Nijikokun (@nijikokun) + * @copyright: Copyright (C) 2011 + * @license: AOL license + */ +public class Methods { + private static String version = null; + private static boolean self = false; + private static Method Method = null; + private static String preferred = ""; + private static Set Methods = new HashSet(); + private static Set Dependencies = new HashSet(); + private static Set Attachables = new HashSet(); + + static { + _init(); + } + + /** + * Implement all methods along with their respective name & class. + */ + private static void _init() { + addMethod("iConomy", new com.nijikokun.register_1_5.payment.methods.iCo6()); + addMethod("iConomy", new com.nijikokun.register_1_5.payment.methods.iCo5()); + addMethod("iConomy", new com.nijikokun.register_1_5.payment.methods.iCo4()); + addMethod("BOSEconomy", new com.nijikokun.register_1_5.payment.methods.BOSE6()); + addMethod("BOSEconomy", new com.nijikokun.register_1_5.payment.methods.BOSE7()); + addMethod("Essentials", new com.nijikokun.register_1_5.payment.methods.EE17()); + addMethod("Currency", new com.nijikokun.register_1_5.payment.methods.MCUR()); + addMethod("3co", new com.nijikokun.register_1_5.payment.methods.ECO3()); + Dependencies.add("MultiCurrency"); + } + + /** + * Used by the plugin to setup version + * + * @param v version + */ + public static void setVersion(String v) { + version = v; + } + + /** + * Use to reset methods during disable + */ + public static void reset() { + version = null; + self = false; + Method = null; + preferred = ""; + Attachables.clear(); + } + + /** + * Use to get version of Register plugin + * + * @return version + */ + public static String getVersion() { + return version; + } + + /** + * Returns an array of payment method names that have been loaded + * through the _init method. + * + * @return Set - Array of payment methods that are loaded. + * @see #setMethod(org.bukkit.plugin.PluginManager) + */ + public static Set getDependencies() { + return Dependencies; + } + + /** + * Interprets Plugin class data to verify whether it is compatible with an existing payment + * method to use for payments and other various economic activity. + * + * @param plugin Plugin data from bukkit, Internal Class file. + * @return Method or Null + */ + public static Method createMethod(Plugin plugin) { + for (Method method : Methods) + if (method.isCompatible(plugin)) { + method.setPlugin(plugin); + return method; + } + + return null; + } + + private static void addMethod(String name, Method method) { + Dependencies.add(name); + Methods.add(method); + } + + /** + * Verifies if Register has set a payment method for usage yet. + * + * @return boolean + * @see #setMethod(org.bukkit.plugin.PluginManager) + * @see #checkDisabled(org.bukkit.plugin.Plugin) + */ + public static boolean hasMethod() { + return (Method != null); + } + + /** + * Checks Plugin Class against a multitude of checks to verify it's usability + * as a payment method. + * + * @param manager the plugin manager for the server + * @return boolean True on success, False on failure. + */ + public static boolean setMethod(PluginManager manager) { + if (hasMethod()) + return true; + + if (self) { + self = false; + return false; + } + + int count = 0; + boolean match = false; + Plugin plugin = null; + + for (String name : getDependencies()) { + if (hasMethod()) + break; + + plugin = manager.getPlugin(name); + if (plugin == null || !plugin.isEnabled()) + continue; + + Method current = createMethod(plugin); + if (current == null) + continue; + + if (preferred.isEmpty()) + Method = current; + else + Attachables.add(current); + } + + if (!preferred.isEmpty()) { + do { + if (hasMethod()) + match = true; + else { + for (Method attached : Attachables) { + if (attached == null) continue; + + if (hasMethod()) { + match = true; break; + } + + if (preferred.isEmpty()) + Method = attached; + + if (count == 0) { + if (preferred.equalsIgnoreCase(attached.getName())) Method = attached; + } else { + Method = attached; + } + } + + count++; + } + } while (!match); + } + + return hasMethod(); + } + + /** + * Sets the preferred economy + * + * @param check The plugin name to check + * @return boolean + */ + public static boolean setPreferred(String check) { + if (getDependencies().contains(check)) { + preferred = check; + return true; + } + + return false; + } + + /** + * Grab the existing and initialized (hopefully) Method Class. + * + * @return Method or Null + */ + public static Method getMethod() { + return Method; + } + + /** + * Verify is a plugin is disabled, only does this if we there is an existing payment + * method initialized in register_1_5. + * + * @param method Plugin data from bukkit, Internal Class file. + * @return boolean + */ + public static boolean checkDisabled(Plugin method) { + if (!hasMethod()) + return true; + + if (Method.isCompatible(method)) + Method = null; + + return (Method == null); + } +} diff --git a/src/com/nijikokun/register_1_5/payment/methods/BOSE6.java b/src/com/nijikokun/register_1_5/payment/methods/BOSE6.java new file mode 100644 index 0000000..f62b52b --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/BOSE6.java @@ -0,0 +1,234 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; + +import cosine.boseconomy.BOSEconomy; +import org.bukkit.plugin.Plugin; + +/** + * BOSEconomy 6 Implementation of Method + * + * @author Nijikokun (@nijikokun) + * @copyright (c) 2011 + * @license AOL license + */ +@SuppressWarnings("deprecation") +public class BOSE6 implements Method { + private BOSEconomy BOSEconomy; + + public BOSEconomy getPlugin() { + return this.BOSEconomy; + } + + public String getName() { + return "BOSEconomy"; + } + + public String getVersion() { + return "0.6.2"; + } + + public int fractionalDigits() { + return 0; + } + + public String format(double amount) { + String currency = this.BOSEconomy.getMoneyNamePlural(); + + if(amount == 1) + currency = this.BOSEconomy.getMoneyName(); + + return amount + " " + currency; + } + + public boolean hasBanks() { + return true; + } + + public boolean hasBank(String bank) { + return this.BOSEconomy.bankExists(bank); + } + + public boolean hasAccount(String name) { + return this.BOSEconomy.playerRegistered(name, false); + } + + public boolean hasBankAccount(String bank, String name) { + return this.BOSEconomy.isBankOwner(bank, name) + || this.BOSEconomy.isBankMember(bank, name); + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + this.BOSEconomy.registerPlayer(name); + return true; + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + this.BOSEconomy.registerPlayer(name); + this.BOSEconomy.setPlayerMoney(name, balance, false); + return true; + } + + public MethodAccount getAccount(String name) { + if(!hasAccount(name)) + return null; + + return new BOSEAccount(name, this.BOSEconomy); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + if(!hasBankAccount(bank, name)) + return null; + + return new BOSEBankAccount(bank, BOSEconomy); + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equalsIgnoreCase("boseconomy") + && plugin instanceof BOSEconomy + && plugin.getDescription().getVersion().equals("0.6.2"); + } + + public void setPlugin(Plugin plugin) { + BOSEconomy = (BOSEconomy) plugin; + } + + public class BOSEAccount implements MethodAccount { + private final String name; + private final BOSEconomy BOSEconomy; + + public BOSEAccount(String name, BOSEconomy bOSEconomy) { + this.name = name; + this.BOSEconomy = bOSEconomy; + } + + public double balance() { + return (double) this.BOSEconomy.getPlayerMoney(this.name); + } + + public boolean set(double amount) { + int IntAmount = (int)Math.ceil(amount); + return this.BOSEconomy.setPlayerMoney(this.name, IntAmount, false); + } + + public boolean add(double amount) { + int IntAmount = (int)Math.ceil(amount); + return this.BOSEconomy.addPlayerMoney(this.name, IntAmount, false); + } + + public boolean subtract(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance - IntAmount), false); + } + + public boolean multiply(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance * IntAmount), false); + } + + public boolean divide(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance / IntAmount), false); + } + + public boolean hasEnough(double amount) { + return (this.balance() >= amount); + } + + public boolean hasOver(double amount) { + return (this.balance() > amount); + } + + public boolean hasUnder(double amount) { + return (this.balance() < amount); + } + + public boolean isNegative() { + return (this.balance() < 0); + } + + public boolean remove() { + return false; + } + } + + public class BOSEBankAccount implements MethodBankAccount { + private final String bank; + private final BOSEconomy BOSEconomy; + + public BOSEBankAccount(String bank, BOSEconomy bOSEconomy) { + this.bank = bank; + this.BOSEconomy = bOSEconomy; + } + + public String getBankName() { + return this.bank; + } + + public int getBankId() { + return -1; + } + + public double balance() { + return (double) this.BOSEconomy.getBankMoney(bank); + } + + public boolean set(double amount) { + int IntAmount = (int)Math.ceil(amount); + return this.BOSEconomy.setBankMoney(bank, IntAmount, true); + } + + public boolean add(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance + IntAmount), false); + } + + public boolean subtract(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance - IntAmount), false); + } + + public boolean multiply(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance * IntAmount), false); + } + + public boolean divide(double amount) { + int IntAmount = (int)Math.ceil(amount); + int balance = (int)this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance / IntAmount), false); + } + + public boolean hasEnough(double amount) { + return (this.balance() >= amount); + } + + public boolean hasOver(double amount) { + return (this.balance() > amount); + } + + public boolean hasUnder(double amount) { + return (this.balance() < amount); + } + + public boolean isNegative() { + return (this.balance() < 0); + } + + public boolean remove() { + return this.BOSEconomy.removeBank(bank); + } + } +} \ No newline at end of file diff --git a/src/com/nijikokun/register_1_5/payment/methods/BOSE7.java b/src/com/nijikokun/register_1_5/payment/methods/BOSE7.java new file mode 100644 index 0000000..7c8179e --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/BOSE7.java @@ -0,0 +1,223 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; + +import cosine.boseconomy.BOSEconomy; +import org.bukkit.plugin.Plugin; + +/** + * BOSEconomy 7 Implementation of Method + * + * @author Acrobot + * @author Nijikokun (@nijikokun) + * @copyright (c) 2011 + * @license AOL license + */ +public class BOSE7 implements Method { + private BOSEconomy BOSEconomy; + + public BOSEconomy getPlugin() { + return this.BOSEconomy; + } + + public String getName() { + return "BOSEconomy"; + } + + public String getVersion() { + return "0.7.0"; + } + + public int fractionalDigits() { + return this.BOSEconomy.getFractionalDigits(); + } + + public String format(double amount) { + String currency = this.BOSEconomy.getMoneyNamePlural(); + + if(amount == 1) + currency = this.BOSEconomy.getMoneyName(); + + return amount + " " + currency; + } + + public boolean hasBanks() { + return true; + } + + public boolean hasBank(String bank) { + return this.BOSEconomy.bankExists(bank); + } + + public boolean hasAccount(String name) { + return this.BOSEconomy.playerRegistered(name, false); + } + + public boolean hasBankAccount(String bank, String name) { + return this.BOSEconomy.isBankOwner(bank, name) || this.BOSEconomy.isBankMember(bank, name); + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + this.BOSEconomy.registerPlayer(name); + return true; + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + this.BOSEconomy.registerPlayer(name); + this.BOSEconomy.setPlayerMoney(name, balance, false); + return true; + } + + public MethodAccount getAccount(String name) { + if(!hasAccount(name)) + return null; + + return new BOSEAccount(name, this.BOSEconomy); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + if(!hasBankAccount(bank, name)) + return null; + + return new BOSEBankAccount(bank, BOSEconomy); + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equalsIgnoreCase("boseconomy") + && plugin instanceof BOSEconomy + && !plugin.getDescription().getVersion().equals("0.6.2"); + } + + public void setPlugin(Plugin plugin) { + BOSEconomy = (BOSEconomy)plugin; + } + + public class BOSEAccount implements MethodAccount { + private String name; + private BOSEconomy BOSEconomy; + + public BOSEAccount(String name, BOSEconomy bOSEconomy) { + this.name = name; + this.BOSEconomy = bOSEconomy; + } + + public double balance() { + return this.BOSEconomy.getPlayerMoneyDouble(this.name); + } + + public boolean set(double amount) { + return this.BOSEconomy.setPlayerMoney(this.name, amount, false); + } + + public boolean add(double amount) { + return this.BOSEconomy.addPlayerMoney(this.name, amount, false); + } + + public boolean subtract(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance - amount), false); + } + + public boolean multiply(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance * amount), false); + } + + public boolean divide(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setPlayerMoney(this.name, (balance / amount), false); + } + + public boolean hasEnough(double amount) { + return (this.balance() >= amount); + } + + public boolean hasOver(double amount) { + return (this.balance() > amount); + } + + public boolean hasUnder(double amount) { + return (this.balance() < amount); + } + + public boolean isNegative() { + return (this.balance() < 0); + } + + public boolean remove() { + return false; + } + } + + public class BOSEBankAccount implements MethodBankAccount { + private String bank; + private BOSEconomy BOSEconomy; + + public BOSEBankAccount(String bank, BOSEconomy bOSEconomy) { + this.bank = bank; + this.BOSEconomy = bOSEconomy; + } + + public String getBankName() { + return this.bank; + } + + public int getBankId() { + return -1; + } + + public double balance() { + return this.BOSEconomy.getBankMoneyDouble(bank); + } + + public boolean set(double amount) { + return this.BOSEconomy.setBankMoney(bank, amount, true); + } + + public boolean add(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance + amount), false); + } + + public boolean subtract(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance - amount), false); + } + + public boolean multiply(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance * amount), false); + } + + public boolean divide(double amount) { + double balance = this.balance(); + return this.BOSEconomy.setBankMoney(bank, (balance / amount), false); + } + + public boolean hasEnough(double amount) { + return (this.balance() >= amount); + } + + public boolean hasOver(double amount) { + return (this.balance() > amount); + } + + public boolean hasUnder(double amount) { + return (this.balance() < amount); + } + + public boolean isNegative() { + return (this.balance() < 0); + } + + public boolean remove() { + return this.BOSEconomy.removeBank(bank); + } + } +} \ No newline at end of file diff --git a/src/com/nijikokun/register_1_5/payment/methods/ECO3.java b/src/com/nijikokun/register_1_5/payment/methods/ECO3.java new file mode 100644 index 0000000..c0873fa --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/ECO3.java @@ -0,0 +1,137 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; +import me.ic3d.eco.ECO; +import org.bukkit.plugin.Plugin; + +/** + * 3co implementation of Method. + * + * @copyright (c) 2011 + * @license AOL license + */ +public class ECO3 implements Method { + private ECO eco; + + public Object getPlugin() { + return this.eco; + } + + public String getName() { + return "3co"; + } + + public String getVersion() { + return "2.0"; + } + + public int fractionalDigits() { + return 0; + } + + public String format(double amount) { + return (int) Math.ceil(amount) + " " + (amount == 1 ? eco.singularCurrency : eco.pluralCurrency); + } + + public boolean hasBanks() { + return false; + } + + public boolean hasBank(String bank) { + return false; + } + + public boolean hasAccount(String name) { + return eco.hasAccount(name); + } + + public boolean hasBankAccount(String bank, String name) { + return false; + } + + public boolean createAccount(String name) { + if (hasAccount(name)) return false; + eco.createAccount(name, 0); + return true; + } + + public boolean createAccount(String name, double balance) { + if (hasAccount(name)) return false; + eco.createAccount(name, (int) balance); + return true; + } + + public MethodAccount getAccount(String name) { + if (!hasAccount(name)) createAccount(name); //Still somehow fails - it's 3co's issue + return new ECO3Account(name); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return null; + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equals("3co") && plugin.getClass().getName().equals("me.ic3d.eco.ECO") && plugin instanceof ECO; + } + + public void setPlugin(Plugin plugin) { + this.eco = (ECO) plugin; + } + + public class ECO3Account implements MethodAccount { + private String name; + + public ECO3Account(String name) { + this.name = name; + } + + public double balance() { + return eco.getMoney(name); + } + + public boolean set(double amount) { + eco.setMoney(name, (int) Math.ceil(amount)); + return true; + } + + public boolean add(double amount) { + set(balance() + amount); + return true; + } + + public boolean subtract(double amount) { + set(balance() - amount); + return true; + } + + public boolean multiply(double amount) { + set(balance() * amount); + return true; + } + + public boolean divide(double amount) { + set(balance() / amount); + return true; + } + + public boolean hasEnough(double amount) { + return eco.hasEnough(name, (int) Math.ceil(amount)); + } + + public boolean hasOver(double amount) { + return balance() > amount; + } + + public boolean hasUnder(double amount) { + return balance() < amount; + } + + public boolean isNegative() { + return balance() < 0; + } + + public boolean remove() { + return false; + } + } +} diff --git a/src/com/nijikokun/register_1_5/payment/methods/EE17.java b/src/com/nijikokun/register_1_5/payment/methods/EE17.java new file mode 100644 index 0000000..c8578e4 --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/EE17.java @@ -0,0 +1,223 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; +import com.earth2me.essentials.Essentials; +import com.earth2me.essentials.api.Economy; + +import org.bukkit.plugin.Plugin; + +/** + * Essentials 17 Implementation of Method + * + * @author Nijikokun (@nijikokun) + * @author Snowleo + * @author Acrobot + * @author KHobbits + * @copyright (c) 2011 + * @license AOL license + */ +public class EE17 implements Method { + private Essentials Essentials; + + public Essentials getPlugin() { + return this.Essentials; + } + + public String getName() { + return "Essentials"; + } + + public String getVersion() { + return "2.2"; + } + + public int fractionalDigits() { + return -1; + } + + public String format(double amount) { + return Economy.format(amount); + } + + public boolean hasBanks() { + return false; + } + + public boolean hasBank(String bank) { + return false; + } + + public boolean hasAccount(String name) { + return Economy.playerExists(name); + } + + public boolean hasBankAccount(String bank, String name) { + return false; + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + Economy.createNPC(name); + return true; + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + Economy.createNPC(name); + + try { + Economy.setMoney(name, balance); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public MethodAccount getAccount(String name) { + if(!hasAccount(name)) + return null; + + return new EEcoAccount(name); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return null; + } + + public boolean isCompatible(Plugin plugin) { + try { Class.forName("com.earth2me.essentials.api.Economy"); } + catch(Exception e) { return false; } + + return plugin.getDescription().getName().equalsIgnoreCase("essentials") + && plugin instanceof Essentials; + } + + public void setPlugin(Plugin plugin) { + Essentials = (Essentials)plugin; + } + + public class EEcoAccount implements MethodAccount { + private String name; + + public EEcoAccount(String name) { + this.name = name; + } + + public double balance() { + Double balance = 0.0; + + try { + balance = Economy.getMoney(this.name); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + } + + return balance; + } + + public boolean set(double amount) { + try { + Economy.setMoney(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public boolean add(double amount) { + try { + Economy.add(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public boolean subtract(double amount) { + try { + Economy.subtract(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public boolean multiply(double amount) { + try { + Economy.multiply(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public boolean divide(double amount) { + try { + Economy.divide(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + return false; + } + + return true; + } + + public boolean hasEnough(double amount) { + try { + return Economy.hasEnough(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + } + + return false; + } + + public boolean hasOver(double amount) { + try { + return Economy.hasMore(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + } + + return false; + } + + public boolean hasUnder(double amount) { + try { + return Economy.hasLess(name, amount); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + } + + return false; + } + + public boolean isNegative() { + try { + return Economy.isNegative(name); + } catch (Exception ex) { + System.out.println("[REGISTER] Error in Essentials Economy: " + ex.getMessage()); + } + + return false; + } + + public boolean remove() { + return false; + } + } +} \ No newline at end of file diff --git a/src/com/nijikokun/register_1_5/payment/methods/MCUR.java b/src/com/nijikokun/register_1_5/payment/methods/MCUR.java new file mode 100644 index 0000000..890b9ee --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/MCUR.java @@ -0,0 +1,150 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; + +import me.ashtheking.currency.Currency; +import me.ashtheking.currency.CurrencyList; + +import org.bukkit.plugin.Plugin; + +/** + * MultiCurrency Method implementation. + * + * @author Acrobot + * @copyright (c) 2011 + * @license AOL license + */ +public class MCUR implements Method { + private Currency currencyList; + + public Object getPlugin() { + return this.currencyList; + } + + public String getName() { + return "MultiCurrency"; + } + + public String getVersion() { + return "0.09"; + } + + public int fractionalDigits() { + return -1; + } + + public String format(double amount) { + return amount + " Currency"; + } + + public boolean hasBanks() { + return true; + } + + public boolean hasBank(String bank) { + return CurrencyList.getAllCurrencys().contains(bank); + } + + public boolean hasAccount(String name) { + return true; + } + + public boolean hasBankAccount(String bank, String name) { + return CurrencyList.getAllCurrencys().contains(bank); + } + + public boolean createAccount(String name) { + CurrencyList.setValue((String) CurrencyList.maxCurrency(name)[0], name, 0); + return true; + } + + public boolean createAccount(String name, double balance) { + CurrencyList.setValue((String) CurrencyList.maxCurrency(name)[0], name, balance); + return true; + } + + public MethodAccount getAccount(String name) { + return new MCurrencyAccount(name); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return new MCurrencyAccount(name, bank); + } + + public boolean isCompatible(Plugin plugin) { + return (plugin.getDescription().getName().equalsIgnoreCase("Currency") + || plugin.getDescription().getName().equalsIgnoreCase("MultiCurrency")) + && plugin instanceof Currency; + } + + public void setPlugin(Plugin plugin) { + currencyList = (Currency) plugin; + } + + public class MCurrencyAccount implements MethodAccount, MethodBankAccount{ + private String name; + private String currency; + + public MCurrencyAccount(String name) { + this.name = name; + this.currency = (String) CurrencyList.maxCurrency(name)[0]; + } + public MCurrencyAccount(String name, String currency) { + this.name = name; + this.currency = currency; + } + + public double balance() { + return CurrencyList.getValue(currency, name); + } + + public String getBankName() { + return currency; + } + + public int getBankId() { + return -1; + } + + public boolean set(double amount) { + CurrencyList.setValue(currency, name, amount); + return true; + } + + public boolean add(double amount) { + return CurrencyList.add(name, amount, currency); + } + + public boolean subtract(double amount) { + return CurrencyList.subtract(name, amount, currency); + } + + public boolean multiply(double amount) { + return CurrencyList.multiply(name, amount, currency); + } + + public boolean divide(double amount) { + return CurrencyList.divide(name, amount, currency); + } + + public boolean hasEnough(double amount) { + return CurrencyList.hasEnough(name, amount, currency); + } + + public boolean hasOver(double amount) { + return CurrencyList.hasOver(name, amount, currency); + } + + public boolean hasUnder(double amount) { + return CurrencyList.hasUnder(name, amount, currency); + } + + public boolean isNegative() { + return CurrencyList.isNegative(name, currency); + } + + public boolean remove() { + return CurrencyList.remove(name, currency); + } + } +} diff --git a/src/com/nijikokun/register_1_5/payment/methods/iCo4.java b/src/com/nijikokun/register_1_5/payment/methods/iCo4.java new file mode 100644 index 0000000..0d488a2 --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/iCo4.java @@ -0,0 +1,167 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; +import com.nijiko.coelho.iConomy.iConomy; +import com.nijiko.coelho.iConomy.system.Account; + + +import org.bukkit.plugin.Plugin; + +/** + * iConomy 4 Implementation of Method + * + * @author Nijikokun (@nijikokun) + * @copyright (c) 2011 + * @license AOL license + */ +public class iCo4 implements Method { + private iConomy iConomy; + + public iConomy getPlugin() { + return this.iConomy; + } + + public String getName() { + return "iConomy"; + } + + public String getVersion() { + return "4"; + } + + public int fractionalDigits() { + return 2; + } + + public String format(double amount) { + return com.nijiko.coelho.iConomy.iConomy.getBank().format(amount); + } + + public boolean hasBanks() { + return false; + } + + public boolean hasBank(String bank) { + return false; + } + + public boolean hasAccount(String name) { + return com.nijiko.coelho.iConomy.iConomy.getBank().hasAccount(name); + } + + public boolean hasBankAccount(String bank, String name) { + return false; + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + try { + com.nijiko.coelho.iConomy.iConomy.getBank().addAccount(name); + } catch(Exception E) { + return false; + } + + return true; + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + try { + com.nijiko.coelho.iConomy.iConomy.getBank().addAccount(name, balance); + } catch(Exception E) { + return false; + } + + return true; + } + + public MethodAccount getAccount(String name) { + return new iCoAccount(com.nijiko.coelho.iConomy.iConomy.getBank().getAccount(name)); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return null; + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equalsIgnoreCase("iconomy") + && plugin.getClass().getName().equals("com.nijiko.coelho.iConomy.iConomy") + && plugin instanceof iConomy; + } + + public void setPlugin(Plugin plugin) { + iConomy = (iConomy)plugin; + } + + public class iCoAccount implements MethodAccount { + private Account account; + + public iCoAccount(Account account) { + this.account = account; + } + + public Account getiCoAccount() { + return account; + } + + public double balance() { + return this.account.getBalance(); + } + + public boolean set(double amount) { + if(this.account == null) return false; + this.account.setBalance(amount); + return true; + } + + public boolean add(double amount) { + if(this.account == null) return false; + this.account.add(amount); + return true; + } + + public boolean subtract(double amount) { + if(this.account == null) return false; + this.account.subtract(amount); + return true; + } + + public boolean multiply(double amount) { + if(this.account == null) return false; + this.account.multiply(amount); + return true; + } + + public boolean divide(double amount) { + if(this.account == null) return false; + this.account.divide(amount); + return true; + } + + public boolean hasEnough(double amount) { + return this.account.hasEnough(amount); + } + + public boolean hasOver(double amount) { + return this.account.hasOver(amount); + } + + public boolean hasUnder(double amount) { + return (this.balance() < amount); + } + + public boolean isNegative() { + return this.account.isNegative(); + } + + public boolean remove() { + if(this.account == null) return false; + this.account.remove(); + return true; + } + } +} diff --git a/src/com/nijikokun/register_1_5/payment/methods/iCo5.java b/src/com/nijikokun/register_1_5/payment/methods/iCo5.java new file mode 100644 index 0000000..47ab56c --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/iCo5.java @@ -0,0 +1,243 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; +import com.iConomy.iConomy; +import com.iConomy.system.Account; +import com.iConomy.system.BankAccount; +import com.iConomy.system.Holdings; +import com.iConomy.util.Constants; + + +import org.bukkit.plugin.Plugin; + +/** + * iConomy 5 Implementation of Method + * + * @author Nijikokun (@nijikokun) + * @copyright (c) 2011 + * @license AOL license + */ +public class iCo5 implements Method { + private iConomy iConomy; + + public iConomy getPlugin() { + return this.iConomy; + } + + public String getName() { + return "iConomy"; + } + + public String getVersion() { + return "5"; + } + + public int fractionalDigits() { + return 2; + } + + public String format(double amount) { + return com.iConomy.iConomy.format(amount); + } + + public boolean hasBanks() { + return Constants.Banking; + } + + public boolean hasBank(String bank) { + return (hasBanks()) && com.iConomy.iConomy.Banks.exists(bank); + } + + public boolean hasAccount(String name) { + return com.iConomy.iConomy.hasAccount(name); + } + + public boolean hasBankAccount(String bank, String name) { + return (hasBank(bank)) && com.iConomy.iConomy.getBank(bank).hasAccount(name); + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + return com.iConomy.iConomy.Accounts.create(name); + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + if(!com.iConomy.iConomy.Accounts.create(name)) + return false; + + getAccount(name).set(balance); + + return true; + } + + public MethodAccount getAccount(String name) { + return new iCoAccount(com.iConomy.iConomy.getAccount(name)); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return new iCoBankAccount(com.iConomy.iConomy.getBank(bank).getAccount(name)); + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equalsIgnoreCase("iconomy") + && plugin.getClass().getName().equals("com.iConomy.iConomy") + && plugin instanceof iConomy; + } + + public void setPlugin(Plugin plugin) { + iConomy = (iConomy)plugin; + } + + public class iCoAccount implements MethodAccount { + private Account account; + private Holdings holdings; + + public iCoAccount(Account account) { + this.account = account; + this.holdings = account.getHoldings(); + } + + public Account getiCoAccount() { + return account; + } + + public double balance() { + return this.holdings.balance(); + } + + public boolean set(double amount) { + if(this.holdings == null) return false; + this.holdings.set(amount); + return true; + } + + public boolean add(double amount) { + if(this.holdings == null) return false; + this.holdings.add(amount); + return true; + } + + public boolean subtract(double amount) { + if(this.holdings == null) return false; + this.holdings.subtract(amount); + return true; + } + + public boolean multiply(double amount) { + if(this.holdings == null) return false; + this.holdings.multiply(amount); + return true; + } + + public boolean divide(double amount) { + if(this.holdings == null) return false; + this.holdings.divide(amount); + return true; + } + + public boolean hasEnough(double amount) { + return this.holdings.hasEnough(amount); + } + + public boolean hasOver(double amount) { + return this.holdings.hasOver(amount); + } + + public boolean hasUnder(double amount) { + return this.holdings.hasUnder(amount); + } + + public boolean isNegative() { + return this.holdings.isNegative(); + } + + public boolean remove() { + if(this.account == null) return false; + this.account.remove(); + return true; + } + } + + public class iCoBankAccount implements MethodBankAccount { + private BankAccount account; + private Holdings holdings; + + public iCoBankAccount(BankAccount account) { + this.account = account; + this.holdings = account.getHoldings(); + } + + public BankAccount getiCoBankAccount() { + return account; + } + + public String getBankName() { + return this.account.getBankName(); + } + + public int getBankId() { + return this.account.getBankId(); + } + + public double balance() { + return this.holdings.balance(); + } + + public boolean set(double amount) { + if(this.holdings == null) return false; + this.holdings.set(amount); + return true; + } + + public boolean add(double amount) { + if(this.holdings == null) return false; + this.holdings.add(amount); + return true; + } + + public boolean subtract(double amount) { + if(this.holdings == null) return false; + this.holdings.subtract(amount); + return true; + } + + public boolean multiply(double amount) { + if(this.holdings == null) return false; + this.holdings.multiply(amount); + return true; + } + + public boolean divide(double amount) { + if(this.holdings == null) return false; + this.holdings.divide(amount); + return true; + } + + public boolean hasEnough(double amount) { + return this.holdings.hasEnough(amount); + } + + public boolean hasOver(double amount) { + return this.holdings.hasOver(amount); + } + + public boolean hasUnder(double amount) { + return this.holdings.hasUnder(amount); + } + + public boolean isNegative() { + return this.holdings.isNegative(); + } + + public boolean remove() { + if(this.account == null) return false; + this.account.remove(); + return true; + } + } +} \ No newline at end of file diff --git a/src/com/nijikokun/register_1_5/payment/methods/iCo6.java b/src/com/nijikokun/register_1_5/payment/methods/iCo6.java new file mode 100644 index 0000000..9a5200e --- /dev/null +++ b/src/com/nijikokun/register_1_5/payment/methods/iCo6.java @@ -0,0 +1,159 @@ +package com.nijikokun.register_1_5.payment.methods; + +import com.nijikokun.register_1_5.payment.Method; +import com.iCo6.iConomy; +import com.iCo6.system.Account; +import com.iCo6.system.Accounts; +import com.iCo6.system.Holdings; + + +import org.bukkit.plugin.Plugin; + +/** + * iConomy 6 Implementation of Method + * + * @author Nijikokun (@nijikokun) + * @copyright (c) 2011 + * @license AOL license + */ +public class iCo6 implements Method { + private iConomy iConomy; + + public iConomy getPlugin() { + return this.iConomy; + } + + public String getName() { + return "iConomy"; + } + + public String getVersion() { + return "6"; + } + + public int fractionalDigits() { + return 2; + } + + public String format(double amount) { + return com.iCo6.iConomy.format(amount); + } + + public boolean hasBanks() { + return false; + } + + public boolean hasBank(String bank) { + return false; + } + + public boolean hasAccount(String name) { + return (new Accounts()).exists(name); + } + + public boolean hasBankAccount(String bank, String name) { + return false; + } + + public boolean createAccount(String name) { + if(hasAccount(name)) + return false; + + return (new Accounts()).create(name); + } + + public boolean createAccount(String name, double balance) { + if(hasAccount(name)) + return false; + + return (new Accounts()).create(name, balance); + } + + public MethodAccount getAccount(String name) { + return new iCoAccount((new Accounts()).get(name)); + } + + public MethodBankAccount getBankAccount(String bank, String name) { + return null; + } + + public boolean isCompatible(Plugin plugin) { + return plugin.getDescription().getName().equalsIgnoreCase("iconomy") + && plugin.getClass().getName().equals("com.iCo6.iConomy") + && plugin instanceof iConomy; + } + + public void setPlugin(Plugin plugin) { + iConomy = (iConomy)plugin; + } + + public class iCoAccount implements MethodAccount { + private Account account; + private Holdings holdings; + + public iCoAccount(Account account) { + this.account = account; + this.holdings = account.getHoldings(); + } + + public Account getiCoAccount() { + return account; + } + + public double balance() { + return this.holdings.getBalance(); + } + + public boolean set(double amount) { + if(this.holdings == null) return false; + this.holdings.setBalance(amount); + return true; + } + + public boolean add(double amount) { + if(this.holdings == null) return false; + this.holdings.add(amount); + return true; + } + + public boolean subtract(double amount) { + if(this.holdings == null) return false; + this.holdings.subtract(amount); + return true; + } + + public boolean multiply(double amount) { + if(this.holdings == null) return false; + this.holdings.multiply(amount); + return true; + } + + public boolean divide(double amount) { + if(this.holdings == null) return false; + this.holdings.divide(amount); + return true; + } + + public boolean hasEnough(double amount) { + return this.holdings.hasEnough(amount); + } + + public boolean hasOver(double amount) { + return this.holdings.hasOver(amount); + } + + public boolean hasUnder(double amount) { + return this.holdings.hasUnder(amount); + } + + public boolean isNegative() { + return this.holdings.isNegative(); + } + + public boolean remove() { + if(this.account == null) return false; + this.account.remove(); + return true; + } + } +} diff --git a/src/com/sk89q/wg_regions_52/ApplicableRegionSet.java b/src/com/sk89q/wg_regions_52/ApplicableRegionSet.java new file mode 100644 index 0000000..b7b6db4 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/ApplicableRegionSet.java @@ -0,0 +1,111 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.sk89q.wg_regions_52; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Represents a set of regions for a particular point or area and the rules + * that are represented by that set. An instance of this can be used to + * query the value of a flag or check if a player can build in the respective + * region or point. This object contains the list of applicable regions and so + * the expensive search of regions that are in the desired area has already + * been completed. + * + * @author sk89q + */ +public class ApplicableRegionSet implements Iterable { + + private Collection applicable; + private Region globalRegion; + + /** + * Construct the object. + * + * @param applicable + * @param globalRegion + */ + public ApplicableRegionSet(Collection applicable, Region globalRegion) { + this.applicable = applicable; + this.globalRegion = globalRegion; + } + + public Collection regions() { + return applicable; + } + + /** + * Clear a region's parents for isFlagAllowed(). + * + * @param needsClear + * @param hasCleared + * @param region + */ + private void clearParents(Set needsClear, Set hasCleared, Region region) { + Region parent = region.getParent(); + + while (parent != null) { + if (!needsClear.remove(parent)) { + hasCleared.add(parent); + } + + parent = parent.getParent(); + } + } + + /** + * Clear a region's parents for getFlag(). + * + * @param needsClear + * @param hasCleared + * @param region + */ + private void clearParents(Map needsClear, Set hasCleared, Region region) { + Region parent = region.getParent(); + + while (parent != null) { + if (needsClear.remove(parent) == null) { + hasCleared.add(parent); + } + + parent = parent.getParent(); + } + } + + /** + * Get the number of regions that are included. + * + * @return + */ + public int size() { + return applicable.size(); + } + + /** + * Get an iterator of affected regions. + * + * @return + */ + public Iterator iterator() { + return applicable.iterator(); + } +} diff --git a/src/com/sk89q/wg_regions_52/BlockVector.java b/src/com/sk89q/wg_regions_52/BlockVector.java new file mode 100644 index 0000000..1679d91 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/BlockVector.java @@ -0,0 +1,111 @@ +package com.sk89q.wg_regions_52; +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +/** + * Extension of Vector that supports being compared as ints (for accuracy). + * + * @author sk89q + */ +public class BlockVector extends Vector { + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockVector(Vector pt) { + super(pt); + } + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public BlockVector(int x, int y, int z) { + super(x, y, z); + } + + /** + * Construct the Vector object. + * + * + * @param x + * @param y + * @param z + */ + public BlockVector(float x, float y, float z) { + super(x, y, z); + } + + /** + * Construct the Vector object. + * + * + * @param x + * @param y + * @param z + */ + public BlockVector(double x, double y, double z) { + super(x, y, z); + } + + + /** + * Construct the Vector object. + * + * + * @param x + * @param y + * @param z + */ + public BlockVector(com.sk89q.worldedit.BlockVector copy) { + super(copy.getX(), copy.getY(), copy.getZ()); + } + + /** + * Checks if another object is equivalent. + * + * @param obj + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector)) { + return false; + } + Vector other = (Vector)obj; + return (int)other.getX() == (int)this.x && (int)other.getY() == (int)this.y + && (int)other.getZ() == (int)this.z; + + } + + /** + * Gets the hash code. + * + * @return hash code + */ + @Override + public int hashCode() { + return (Integer.valueOf((int)x).hashCode() >> 13) ^ + (Integer.valueOf((int)y).hashCode() >> 7) ^ + Integer.valueOf((int)z).hashCode(); + } +} diff --git a/src/com/sk89q/wg_regions_52/BlockVector2D.java b/src/com/sk89q/wg_regions_52/BlockVector2D.java new file mode 100644 index 0000000..635b971 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/BlockVector2D.java @@ -0,0 +1,91 @@ +package com.sk89q.wg_regions_52; +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +/** + * Extension of Vector2D that supports being compared as ints (for accuracy). + * + * @author sk89q + */ +public class BlockVector2D extends Vector2D { + /** + * Construct the Vector object. + * + * @param pt + */ + public BlockVector2D(Vector2D pt) { + super(pt); + } + + /** + * Construct the Vector object. + * + * @param x + * @param z + */ + public BlockVector2D(int x, int z) { + super(x, z); + } + + /** + * Construct the Vector object. + * + * @param x + * @param z + */ + public BlockVector2D(float x, float z) { + super(x, z); + } + + /** + * Construct the Vector object. + * + * @param x + * @param z + */ + public BlockVector2D(double x, double z) { + super(x, z); + } + + /** + * Checks if another object is equivalent. + * + * @param obj + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector2D)) { + return false; + } + Vector2D other = (Vector2D)obj; + return (int)other.x == (int)this.x && (int)other.z == (int)this.z; + + } + + /** + * Gets the hash code. + * + * @return hash code + */ + @Override + public int hashCode() { + return (Integer.valueOf((int)x).hashCode() >> 13) ^ + Integer.valueOf((int)z).hashCode(); + } +} diff --git a/src/com/sk89q/wg_regions_52/BukkitUtil.java b/src/com/sk89q/wg_regions_52/BukkitUtil.java new file mode 100644 index 0000000..09524be --- /dev/null +++ b/src/com/sk89q/wg_regions_52/BukkitUtil.java @@ -0,0 +1,109 @@ + +package com.sk89q.wg_regions_52; +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import java.util.*; + +import org.bukkit.block.*; +import org.bukkit.entity.Player; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; + +public class BukkitUtil { + private BukkitUtil() { + } + +// private static final Map wlw = new HashMap(); +// public static LocalWorld getLocalWorld(World w) { +// LocalWorld lw = wlw.get(w); +// if (lw == null) { +// lw = new BukkitWorld(w); +// wlw.put(w, lw); +// } +// return lw; +// } + + public static BlockVector toVector(Block block) { + return new BlockVector(block.getX(), block.getY(), block.getZ()); + } + + public static BlockVector toVector(BlockFace face) { + return new BlockVector(face.getModX(), face.getModY(), face.getModZ()); + } + +// public static BlockWorldVector toWorldVector(Block block) { +// return new BlockWorldVector(getLocalWorld(block.getWorld()), block.getX(), block.getY(), block.getZ()); +// } + + public static Vector toVector(Location loc) { + return new Vector(loc.getX(), loc.getY(), loc.getZ()); + } + + public static Vector toVector(org.bukkit.util.Vector vector) { + return new Vector(vector.getX(), vector.getY(), vector.getZ()); + } + +// public static Location toLocation(WorldVector pt) { +// return new Location(toWorld(pt), pt.getX(), pt.getY(), pt.getZ()); +// } + + public static Location toLocation(World world, Vector pt) { + return new Location(world, pt.getX(), pt.getY(), pt.getZ()); + } + + public static Location center(Location loc) { + return new Location( + loc.getWorld(), + loc.getBlockX()+0.5, + loc.getBlockY()+0.5, + loc.getBlockZ()+0.5, + loc.getPitch(), + loc.getYaw() + ); + } + + public static Player matchSinglePlayer(Server server, String name) { + List players = server.matchPlayer(name); + if (players.size() == 0) { + return null; + } + return players.get(0); + } + +// public static Block toBlock(BlockWorldVector pt) { +// return toWorld(pt).getBlockAt(toLocation(pt)); +// } +// +// public static World toWorld(WorldVector pt) { +// return ((BukkitWorld)pt.getWorld()).getWorld(); +// } + + /** + * Bukkit's Location class has serious problems with floating point + * precision. + */ + public static boolean equals(Location a, Location b) { + if (Math.abs(a.getX()-b.getX()) > EQUALS_PRECISION) return false; + if (Math.abs(a.getY()-b.getY()) > EQUALS_PRECISION) return false; + if (Math.abs(a.getZ()-b.getZ()) > EQUALS_PRECISION) return false; + return true; + } + public static final double EQUALS_PRECISION = 0.0001; +} diff --git a/src/com/sk89q/wg_regions_52/CuboidRegion.java b/src/com/sk89q/wg_regions_52/CuboidRegion.java new file mode 100644 index 0000000..8931c1b --- /dev/null +++ b/src/com/sk89q/wg_regions_52/CuboidRegion.java @@ -0,0 +1,298 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.sk89q.wg_regions_52; + +import com.sk89q.worldedit.*; +//import com.sk89q.worldguard.protection.UnsupportedIntersectionException; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Location; + +/** + * Represents a cuboid region that can be . + * + * @author sk89q + */ +public class CuboidRegion extends Region { + + /** + * Store the first point. + */ + private BlockVector min; + /** + * Store the second point. + */ + private BlockVector max; + + /** + * Construct a new instance of this cuboid region. + * + * @param id + * @param min + * @param max + */ + public CuboidRegion(String id, BlockVector min, BlockVector max) { + super(id); + this.min = min; + this.max = max; + } + + /** + * Construct a new instance of this cuboid region. + * + * @param id + * @param min + * @param max + */ + public CuboidRegion(String id, com.sk89q.worldedit.BlockVector min, com.sk89q.worldedit.BlockVector max) { + super(id); + this.min = new BlockVector(min); + this.max = new BlockVector(max); + } + + /** + * Get the lower point of the cuboid. + * + * @return min point + */ + @Override + public BlockVector getMinimumPoint() { + return min; + } + + /** + * Set the lower point of the cuboid. + * + * @param pt + */ + public void setMinimumPoint(BlockVector pt) { + min = pt; + } + + /** + * Get the upper point of the cuboid. + * + * @return max point + */ + @Override + public BlockVector getMaximumPoint() { + return max; + } + + /** + * Set the upper point of the cuboid. + * + * @param pt + */ + public void setMaximumPoint(BlockVector pt) { + max = pt; + } + + /** + * Checks to see if a point is inside this region. + */ + @Override + public boolean contains(Vector pt) { + final int x = pt.getBlockX(); + final int y = pt.getBlockY(); + final int z = pt.getBlockZ(); + return x >= min.getBlockX() && x <= max.getBlockX() + && y >= min.getBlockY() && y <= max.getBlockY() + && z >= min.getBlockZ() && z <= max.getBlockZ(); + } + + + @Override + public boolean contains(Location loc) { + final int x = loc.getBlockX(); + final int y = loc.getBlockY(); + final int z = loc.getBlockZ(); + return x >= min.getBlockX() && x <= max.getBlockX() + && y >= min.getBlockY() && y <= max.getBlockY() + && z >= min.getBlockZ() && z <= max.getBlockZ(); + } + + /* + public boolean intersectsWith(Region region) throws UnsupportedIntersectionException { + + if (region instanceof CuboidRegion) { + CuboidRegion r1 = (CuboidRegion) this; + CuboidRegion r2 = (CuboidRegion) region; + BlockVector min1 = r1.getMinimumPoint(); + BlockVector max1 = r1.getMaximumPoint(); + BlockVector min2 = r2.getMinimumPoint(); + BlockVector max2 = r2.getMaximumPoint(); + + return !(min1.getBlockX() > max2.getBlockX() + || min1.getBlockY() > max2.getBlockY() + || min1.getBlockZ() > max2.getBlockZ() + || max1.getBlockX() < min2.getBlockX() + || max1.getBlockY() < min2.getBlockY() + || max1.getBlockZ() < min2.getBlockZ()); + } else if (region instanceof PolygonalRegion) { + throw new UnsupportedIntersectionException(); + } else { + throw new UnsupportedIntersectionException(); + } + } + */ + + @Override + public List getIntersectingRegions(List regions){// throws UnsupportedIntersectionException { + final int numRegions = regions.size(); + final List intersectingRegions = new ArrayList(); + int i, i2, i3; + + for (i = 0; i < numRegions; i++) { + final Region region = regions.get(i); + final BlockVector rMinPoint = region.getMinimumPoint(); + final BlockVector rMaxPoint = region.getMaximumPoint(); + + // Check whether the region is outside the min and max vector + if ((rMinPoint.getBlockX() < min.getBlockX() && rMaxPoint.getBlockX() < min.getBlockX()) + || (rMinPoint.getBlockX() > max.getBlockX() && rMaxPoint.getBlockX() > max.getBlockX()) + && ((rMinPoint.getBlockY() < min.getBlockY() && rMaxPoint.getBlockY() < min.getBlockY()) + || (rMinPoint.getBlockY() > max.getBlockY() && rMaxPoint.getBlockY() > max.getBlockY())) + && ((rMinPoint.getBlockZ() < min.getBlockZ() && rMaxPoint.getBlockZ() < min.getBlockZ()) + || (rMinPoint.getBlockZ() > max.getBlockZ() && rMaxPoint.getBlockZ() > max.getBlockZ())) ) { + //intersectingRegions.add(regions.get(i)); + continue; + } + + // Check whether the regions points are inside the other region + if (region.contains(new Vector(min.getBlockX(), min.getBlockY(), min.getBlockZ())) + || region.contains(new Vector(min.getBlockX(), min.getBlockY(), max.getBlockZ())) + || region.contains(new Vector(min.getBlockX(), max.getBlockY(), max.getBlockZ())) + || region.contains(new Vector(min.getBlockX(), max.getBlockY(), min.getBlockZ())) + || region.contains(new Vector(max.getBlockX(), max.getBlockY(), max.getBlockZ())) + || region.contains(new Vector(max.getBlockX(), max.getBlockY(), min.getBlockZ())) + || region.contains(new Vector(max.getBlockX(), min.getBlockY(), min.getBlockZ())) + || region.contains(new Vector(max.getBlockX(), min.getBlockY(), max.getBlockZ())) ) { + intersectingRegions.add(regions.get(i)); + continue; + } + + // Check whether the other regions points are inside the current region + if (region instanceof PolygonalRegion) { + for (i2 = 0; i2 < ((PolygonalRegion)region).getPoints().size(); i2++) { + final BlockVector2D pt2Dr = ((PolygonalRegion)region).getPoints().get(i2); + final int minYr = ((PolygonalRegion)region).minY; + final int maxYr = ((PolygonalRegion)region).maxY; + final Vector ptr = new Vector(pt2Dr.getBlockX(), minYr, pt2Dr.getBlockZ()); + final Vector ptr2 = new Vector(pt2Dr.getBlockX(), maxYr, pt2Dr.getBlockZ()); + + if (this.contains(ptr) || this.contains(ptr2)) { + intersectingRegions.add(regions.get(i)); + continue; + } + } + } else if (region instanceof CuboidRegion) { + final BlockVector ptcMin = region.getMinimumPoint(); + final BlockVector ptcMax = region.getMaximumPoint(); + + if (this.contains(new Vector(ptcMin.getBlockX(), ptcMin.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMin.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMax.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMax.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMax.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMax.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMin.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMin.getBlockY(), ptcMax.getBlockZ())) ) { + intersectingRegions.add(regions.get(i)); + continue; + } + } else { + throw new UnsupportedOperationException("Not supported yet."); + } + + // Check whether the current regions edges collide with the regions edges + boolean regionIsIntersecting = false; + final List points = new ArrayList(); + points.add(new BlockVector2D(min.getBlockX(), min.getBlockZ())); + points.add(new BlockVector2D(min.getBlockX(), max.getBlockZ())); + points.add(new BlockVector2D(max.getBlockX(), max.getBlockZ())); + points.add(new BlockVector2D(max.getBlockX(), min.getBlockZ())); + + for (i2 = 0; i2 < points.size(); i2++) { + boolean checkNextPoint = false; + final BlockVector2D currPoint = points.get(i2); + final BlockVector2D nextPoint = (i2 == points.size() - 1 ? points.get(0) : points.get(i2 + 1)); + int currX = currPoint.getBlockX(); + int currZ = currPoint.getBlockZ(); + while (!checkNextPoint) { + for(i3 = min.getBlockY(); i3 <= max.getBlockY(); i3++) { + if (region.contains(new Vector(currX, i3, currZ))) { + intersectingRegions.add(regions.get(i)); + regionIsIntersecting = true; + break; + } + } + + if (currX == nextPoint.getBlockX() || currZ == nextPoint.getBlockZ() || regionIsIntersecting) { + checkNextPoint = true; + } + + if (nextPoint.getBlockX() > currPoint.getBlockX()) { + currX++; + } else { + currX--; + } + if (nextPoint.getBlockZ() > currPoint.getBlockZ()) { + currZ++; + } else { + currZ--; + } + } + + if (regionIsIntersecting) { + break; + } + } + } + + return intersectingRegions; + } + + + /** + * Return the type of region as a user-friendly name. + * + * @return type of region + */ + @Override + public String getTypeName() { + return "cuboid"; + } + + /** + * Get the number of Blocks in this region + * + * @return + */ + @Override + public int volume() { + final int xLength = max.getBlockX() - min.getBlockX() + 1; + final int yLength = max.getBlockY() - min.getBlockY() + 1; + final int zLength = max.getBlockZ() - min.getBlockZ() + 1; + final int volume = xLength * yLength * zLength; + return volume; + } + + +} diff --git a/src/com/sk89q/wg_regions_52/GlobalRegion.java b/src/com/sk89q/wg_regions_52/GlobalRegion.java new file mode 100644 index 0000000..4f42b56 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/GlobalRegion.java @@ -0,0 +1,68 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52; + +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Location; + +public class GlobalRegion extends Region { + + public GlobalRegion(String id) { + super(id); + } + + @Override + public BlockVector getMinimumPoint() { + return new BlockVector(0, 0, 0); + } + + @Override + public BlockVector getMaximumPoint() { + return new BlockVector(0, 0, 0); + } + + @Override + public int volume() { + return 0; + } + + @Override + public boolean contains(Vector pt) { + return false; + } + + @Override + public boolean contains(Location loc) { + return false; + } + + @Override + public String getTypeName() { + return "global"; + } + + @Override + public List getIntersectingRegions(List regions){ + //throws UnsupportedIntersectionException { + return new ArrayList(); + } + +} diff --git a/src/com/sk89q/wg_regions_52/PolygonalRegion.java b/src/com/sk89q/wg_regions_52/PolygonalRegion.java new file mode 100644 index 0000000..df6bd9e --- /dev/null +++ b/src/com/sk89q/wg_regions_52/PolygonalRegion.java @@ -0,0 +1,328 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.sk89q.wg_regions_52; + +import java.util.List; +import java.util.ArrayList; +import org.bukkit.Location; + +public class PolygonalRegion extends Region { + + List points; + int minY; + int maxY; + private BlockVector min; + private BlockVector max; + + public PolygonalRegion(String id, List/**/ points, int minY, int maxY) { + super(id); + if(points == null || points.isEmpty()) { + throw new IllegalArgumentException("Points cannot be empty"); + } + Object p1 = points.get(0); + boolean useWE = p1 instanceof com.sk89q.worldedit.BlockVector2D; + if(!useWE && !(p1 instanceof BlockVector2D)) { + throw new IllegalArgumentException("Points must be of type BlockVector2D"); + } + + this.points = points; + this.minY = minY; + this.maxY = maxY; + + + int minX = useWE ? ((com.sk89q.worldedit.BlockVector2D) p1).getBlockX() + : ((BlockVector2D) p1).getBlockX(); + int minZ = useWE ? ((com.sk89q.worldedit.BlockVector2D) p1).getBlockZ() + : ((BlockVector2D) p1).getBlockZ(); + int maxX = minX; + int maxZ = minZ; + + for (final Object v : points) { + int x = useWE ? ((com.sk89q.worldedit.BlockVector2D) v).getBlockX() + : ((BlockVector2D) v).getBlockX(); + int z = useWE ? ((com.sk89q.worldedit.BlockVector2D) v).getBlockZ() + : ((BlockVector2D) v).getBlockZ(); + if (x < minX) { + minX = x; + } + if (z < minZ) { + minZ = z; + } + if (x > maxX) { + maxX = x; + } + if (z > maxZ) { + maxZ = z; + } + } + + min = new BlockVector(minX, minY, minZ); + max = new BlockVector(maxX, maxY, maxZ); + } + + public List getPoints() { + return points; + } + + @Override + public BlockVector getMinimumPoint() { + return min; + } + + @Override + public BlockVector getMaximumPoint() { + return max; + } + + /** + * Checks to see if a point is inside this region. + */ + @Override + public boolean contains(Vector pt) { + return contains(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); + } + + + @Override + public boolean contains(Location loc) { + return contains(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + private boolean contains(int targetX, int targetY, int targetZ){ + + if (targetY < minY || targetY > maxY) { + return false; + } + //Quick and dirty check. + if (targetX < min.getBlockX() || targetX > max.getBlockX() || targetZ < min.getBlockZ() || targetZ > max.getBlockZ()) { + return false; + } + boolean inside = false; + final int npoints = points.size(); + int xNew, zNew; + int xOld, zOld; + int x1, z1; + int x2, z2; + int i; + + xOld = points.get(npoints - 1).getBlockX(); + zOld = points.get(npoints - 1).getBlockZ(); + + for (i = 0; i < npoints; i++) { + xNew = points.get(i).getBlockX(); + zNew = points.get(i).getBlockZ(); + //Check for corner + if (xNew == targetX && zNew == targetZ) { + return true; + } + if (xNew > xOld) { + x1 = xOld; + x2 = xNew; + z1 = zOld; + z2 = zNew; + } else { + x1 = xNew; + x2 = xOld; + z1 = zNew; + z2 = zOld; + } + if ((xNew < targetX) == (targetX <= xOld) + && ((long) targetZ - (long) z1) * (long) (x2 - x1) <= ((long) z2 - (long) z1) + * (long) (targetX - x1)) { + inside = !inside; + } + xOld = xNew; + zOld = zNew; + } + + return inside; + } + + @Override + public List getIntersectingRegions(List regions){// throws UnsupportedIntersectionException { + final int numRegions = regions.size(); + final int numPoints = points.size(); + final List intersectingRegions = new ArrayList(); + int i, i2, i3; + + for (i = 0; i < numRegions; i++) { + Region region = regions.get(i); + final BlockVector rMinPoint = region.getMinimumPoint(); + final BlockVector rMaxPoint = region.getMaximumPoint(); + + // Check whether the region is outside the min and max vector + if ((rMinPoint.getBlockX() < min.getBlockX() && rMaxPoint.getBlockX() < min.getBlockX()) + || (rMinPoint.getBlockX() > max.getBlockX() && rMaxPoint.getBlockX() > max.getBlockX()) + && ((rMinPoint.getBlockY() < min.getBlockY() && rMaxPoint.getBlockY() < min.getBlockY()) + || (rMinPoint.getBlockY() > max.getBlockY() && rMaxPoint.getBlockY() > max.getBlockY())) + && ((rMinPoint.getBlockZ() < min.getBlockZ() && rMaxPoint.getBlockZ() < min.getBlockZ()) + || (rMinPoint.getBlockZ() > max.getBlockZ() && rMaxPoint.getBlockZ() > max.getBlockZ())) ) { + intersectingRegions.add(regions.get(i)); + continue; + } + + // Check whether the regions points are inside the other region + for (i2 = 0; i < numPoints; i++) { + final Vector pt = new Vector(points.get(i2).getBlockX(), minY, points.get(i2).getBlockZ()); + final Vector pt2 = new Vector(points.get(i2).getBlockX(), maxY, points.get(i2).getBlockZ()); + if (region.contains(pt) || region.contains(pt2)) { + intersectingRegions.add(regions.get(i)); + continue; + } + } + + // Check whether the other regions points are inside the current region + if (region instanceof PolygonalRegion) { + for (i2 = 0; i < ((PolygonalRegion)region).getPoints().size(); i++) { + final BlockVector2D pt2Dr = ((PolygonalRegion)region).getPoints().get(i2); + final int minYr = ((PolygonalRegion)region).minY; + final int maxYr = ((PolygonalRegion)region).maxY; + final Vector ptr = new Vector(pt2Dr.getBlockX(), minYr, pt2Dr.getBlockZ()); + final Vector ptr2 = new Vector(pt2Dr.getBlockX(), maxYr, pt2Dr.getBlockZ()); + + if (this.contains(ptr) || this.contains(ptr2)) { + intersectingRegions.add(regions.get(i)); + continue; + } + } + } else if (region instanceof CuboidRegion) { + final BlockVector ptcMin = region.getMinimumPoint(); + final BlockVector ptcMax = region.getMaximumPoint(); + + if (this.contains(new Vector(ptcMin.getBlockX(), ptcMin.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMin.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMax.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMin.getBlockX(), ptcMax.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMax.getBlockY(), ptcMax.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMax.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMin.getBlockY(), ptcMin.getBlockZ())) + || this.contains(new Vector(ptcMax.getBlockX(), ptcMin.getBlockY(), ptcMax.getBlockZ())) ) { + intersectingRegions.add(regions.get(i)); + continue; + } + } else { + throw new UnsupportedOperationException("Not supported yet."); + } + + // Check whether the current regions edges collide with the regions edges + boolean regionIsIntersecting = false; + for (i2 = 0; i2 < numPoints; i2++) { + boolean checkNextPoint = false; + final BlockVector2D currPoint = points.get(i2); + BlockVector2D nextPoint; + + if (i2 == (numPoints - 1)) { + nextPoint = points.get(0); + } else { + nextPoint = points.get(i2 + 1); + } + + int currX = currPoint.getBlockX(); + int currZ = currPoint.getBlockZ(); + + while (!checkNextPoint) { + for(i3 = this.minY; i3 <= this.maxY; i3++) { + if (region.contains(new Vector(currX, i3, currZ))) { + intersectingRegions.add(regions.get(i)); + regionIsIntersecting = true; + break; + } + } + + if (currX == nextPoint.getBlockX() || currZ == nextPoint.getBlockZ() || regionIsIntersecting) { + checkNextPoint = true; + } + + if (nextPoint.getBlockX() > currPoint.getBlockX()) { + currX++; + } else { + currX--; + } + if (nextPoint.getBlockZ() > currPoint.getBlockZ()) { + currZ++; + } else { + currZ--; + } + } + + if (regionIsIntersecting) { + break; + } + } + } + + return intersectingRegions; + } + + + /** + * Return the type of region as a user-friendly name. + * + * @return type of region + */ + @Override + public String getTypeName() { + return "polygon"; + } + + /** + * Get the number of Blocks in this region + * + * @return + */ + @Override + public int volume() { + int volume = 0; + int numPoints = points.size(); + if (numPoints < 3) { + return 0; + } + + double area = 0; + int xa, z1, z2; + + for (int i = 0; i <= numPoints - 1; i++) { + xa = points.get(i).getBlockX(); + //za = points.get(i).getBlockZ(); + + if (points.get(i + 1) == null) { + z1 = points.get(0).getBlockZ(); + } else { + z1 = points.get(i + 1).getBlockZ(); + } + if (points.get(i - 1) == null) { + z2 = points.get(numPoints - 1).getBlockZ(); + } else { + z2 = points.get(i - 1).getBlockZ(); + } + + area = area + (xa * (z1 - z2)); + } + + xa = points.get(0).getBlockX(); + //za = points.get(0).getBlockZ(); + + area = area + (xa * (points.get(1).getBlockZ() - points.get(numPoints - 1).getBlockZ())); + + volume = (Math.abs(maxY - minY) + 1) * (int) Math.ceil((Math.abs(area) / 2)); + + return volume; + } + +} diff --git a/src/com/sk89q/wg_regions_52/Region.java b/src/com/sk89q/wg_regions_52/Region.java new file mode 100644 index 0000000..7ae2683 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/Region.java @@ -0,0 +1,199 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52; + +import java.util.List; +import java.util.regex.Pattern; +import org.bukkit.Location; + +/** + * Represents a region of any shape and size that can be protected. + * + * @author sk89q + */ +public abstract class Region implements Comparable { + + private static final Pattern idPattern = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); + + /** + * Holds the region's ID. + */ + private String id; + + /** + * Holds the curParent. + */ + private Region parent; + + private String extraInfo = null; + /** + * Construct a new instance of this region. + * + * @param id + */ + public Region(String id) { + this.id = id; + } + + /** + * @return the id + */ + public String getId() { + return id; + } + + public String getInfo(){ + return extraInfo; + } + + public void setInfo(String extraInfo) { + this.extraInfo = extraInfo; + } + + /** + * Get the lower point of the cuboid. + * + * @return min point + */ + public abstract BlockVector getMinimumPoint(); + + /** + * Get the upper point of the cuboid. + * + * @return max point + */ + public abstract BlockVector getMaximumPoint(); + + /** + * @return the curParent + */ + public Region getParent() { + return parent; + } + + /** + * Set the curParent. This checks to make sure that it will not result + * in circular inheritance. + * + * @param parent the curParent to setFlag + * @throws CircularInheritanceException + */ + public void setParent(Region parent) throws CircularInheritanceException { + if (parent == null) { + this.parent = null; + return; + } + + if (parent == this) { + throw new CircularInheritanceException(); + } + + Region p = parent.getParent(); + while (p != null) { + if (p == this) { + throw new CircularInheritanceException(); + } + p = p.getParent(); + } + + this.parent = parent; + } + + /** + * Get the number of blocks in this region + * + * @return + */ + public abstract int volume(); + + /** + * Check to see if a point is inside this region. + * + * @param pt + * @return + */ + public abstract boolean contains(Vector pt); + + public abstract boolean contains(Location loc); + /** + * Compares to another region. + * + * @param other + * @return + */ + public int compareTo(Region other) { + return id.compareToIgnoreCase(other.getId()); + } + + /** + * Return the type of region as a user-friendly, lowercase name. + * + * @return type of region + */ + public abstract String getTypeName(); + + /** + * Get a list of intersecting regions. + * + * @param regions + * @return + */ + public abstract List getIntersectingRegions(List regions); + + /** + * Checks to see if the given ID is accurate. + * + * @param id + * @return + */ + public static boolean isValidId(String id) { + return idPattern.matcher(id).matches(); + } + + /** + * Returns the hash code. + */ + @Override + public int hashCode(){ + return id.hashCode(); + } + + /** + * Returns whether this region has the same ID as another region. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Region)) { + return false; + } + + final Region other = (Region) obj; + return other.getId().equals(getId()); + } + + /** + * Thrown when setting a curParent would create a circular inheritance + * situation. + * + */ + public static class CircularInheritanceException extends Exception { + private static final long serialVersionUID = 7479613488496776022L; + } +} diff --git a/src/com/sk89q/wg_regions_52/Vector.java b/src/com/sk89q/wg_regions_52/Vector.java new file mode 100644 index 0000000..356460f --- /dev/null +++ b/src/com/sk89q/wg_regions_52/Vector.java @@ -0,0 +1,637 @@ +package com.sk89q.wg_regions_52; +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +/** + * + * @author sk89q + */ +public class Vector { + protected final double x, y, z; + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public Vector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public Vector(int x, int y, int z) { + this.x = (double)x; + this.y = (double)y; + this.z = (double)z; + } + + /** + * Construct the Vector object. + * + * @param x + * @param y + * @param z + */ + public Vector(float x, float y, float z) { + this.x = (double)x; + this.y = (double)y; + this.z = (double)z; + } + + /** + * Construct the Vector object. + * + * @param pt + */ + public Vector(Vector pt) { + this.x = pt.x; + this.y = pt.y; + this.z = pt.z; + } + + /** + * Construct the Vector object. + */ + public Vector() { + this.x = 0; + this.y = 0; + this.z = 0; + } + + /** + * @return the x + */ + public double getX() { + return x; + } + + /** + * @return the x + */ + public int getBlockX() { + return (int)Math.round(x); + } + + /** + * Set X. + * + * @param x + * @return new vector + */ + public Vector setX(double x) { + return new Vector(x, y, z); + } + + /** + * Set X. + * + * @param x + * @return new vector + */ + public Vector setX(int x) { + return new Vector(x, y, z); + } + + /** + * @return the y + */ + public double getY() { + return y; + } + + /** + * @return the y + */ + public int getBlockY() { + return (int)Math.round(y); + } + + /** + * Set Y. + * + * @param y + * @return new vector + */ + public Vector setY(double y) { + return new Vector(x, y, z); + } + + /** + * Set Y. + * + * @param y + * @return new vector + */ + public Vector setY(int y) { + return new Vector(x, y, z); + } + + /** + * @return the z + */ + public double getZ() { + return z; + } + + /** + * @return the z + */ + public int getBlockZ() { + return (int)Math.round(z); + } + + /** + * Set Z. + * + * @param z + * @return new vector + */ + public Vector setZ(double z) { + return new Vector(x, y, z); + } + + /** + * Set Z. + * + * @param z + * @return new vector + */ + public Vector setZ(int z) { + return new Vector(x, y, z); + } + + /** + * Adds two points. + * + * @param other + * @return New point + */ + public Vector add(Vector other) { + return new Vector(x + other.x, y + other.y, z + other.z); + } + + /** + * Adds two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector add(double x, double y, double z) { + return new Vector(this.x + x, this.y + y, this.z + z); + } + + /** + * Adds two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector add(int x, int y, int z) { + return new Vector(this.x + x, this.y + y, this.z + z); + } + + /** + * Adds points. + * + * @param others + * @return New point + */ + public Vector add(Vector ... others) { + double newX = x, newY = y, newZ = z; + + for (int i = 0; i < others.length; ++i) { + newX += others[i].x; + newY += others[i].y; + newZ += others[i].z; + } + return new Vector(newX, newY, newZ); + } + + /** + * Subtracts two points. + * + * @param other + * @return New point + */ + public Vector subtract(Vector other) { + return new Vector(x - other.x, y - other.y, z - other.z); + } + + /** + * Subtract two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector subtract(double x, double y, double z) { + return new Vector(this.x - x, this.y - y, this.z - z); + } + + /** + * Subtract two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector subtract(int x, int y, int z) { + return new Vector(this.x - x, this.y - y, this.z - z); + } + + /** + * Subtract points. + * + * @param others + * @return New point + */ + public Vector subtract(Vector ... others) { + double newX = x, newY = y, newZ = z; + + for (int i = 0; i < others.length; ++i) { + newX -= others[i].x; + newY -= others[i].y; + newZ -= others[i].z; + } + return new Vector(newX, newY, newZ); + } + + /** + * Multiplies two points. + * + * @param other + * @return New point + */ + public Vector multiply(Vector other) { + return new Vector(x * other.x, y * other.y, z * other.z); + } + + /** + * Multiply two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector multiply(double x, double y, double z) { + return new Vector(this.x * x, this.y * y, this.z * z); + } + + /** + * Multiply two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector multiply(int x, int y, int z) { + return new Vector(this.x * x, this.y * y, this.z * z); + } + + /** + * Multiply points. + * + * @param others + * @return New point + */ + public Vector multiply(Vector ... others) { + double newX = x, newY = y, newZ = z; + + for (int i = 0; i < others.length; ++i) { + newX *= others[i].x; + newY *= others[i].y; + newZ *= others[i].z; + } + return new Vector(newX, newY, newZ); + } + + /** + * Scalar multiplication. + * + * @param n + * @return New point + */ + public Vector multiply(double n) { + return new Vector(this.x * n, this.y * n, this.z * n); + } + + /** + * Scalar multiplication. + * + * @param n + * @return New point + */ + public Vector multiply(float n) { + return new Vector(this.x * n, this.y * n, this.z * n); + } + + /** + * Scalar multiplication. + * + * @param n + * @return New point + */ + public Vector multiply(int n) { + return new Vector(this.x * n, this.y * n, this.z * n); + } + + /** + * Divide two points. + * + * @param other + * @return New point + */ + public Vector divide(Vector other) { + return new Vector(x / other.x, y / other.y, z / other.z); + } + + /** + * Divide two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector divide(double x, double y, double z) { + return new Vector(this.x / x, this.y / y, this.z / z); + } + + /** + * Divide two points. + * + * @param x + * @param y + * @param z + * @return New point + */ + public Vector divide(int x, int y, int z) { + return new Vector(this.x / x, this.y / y, this.z / z); + } + + /** + * Scalar division. + * + * @param n + * @return new point + */ + public Vector divide(int n) { + return new Vector(x / n, y / n, z / n); + } + + /** + * Scalar division. + * + * @param n + * @return new point + */ + public Vector divide(double n) { + return new Vector(x / n, y / n, z / n); + } + + /** + * Scalar division. + * + * @param n + * @return new point + */ + public Vector divide(float n) { + return new Vector(x / n, y / n, z / n); + } + + /** + * Get the length of the vector. + * + * @return distance + */ + public double length() { + return Math.sqrt(Math.pow(x, 2) + + Math.pow(y, 2) + + Math.pow(z, 2)); + } + + /** + * Get the distance away from a point. + * + * @param pt + * @return distance + */ + public double distance(Vector pt) { + return Math.sqrt(Math.pow(pt.x - x, 2) + + Math.pow(pt.y - y, 2) + + Math.pow(pt.z - z, 2)); + } + + /** + * Get the distance away from a point, squared. + * + * @param pt + * @return distance + */ + public double distanceSq(Vector pt) { + return Math.pow(pt.x - x, 2) + + Math.pow(pt.y - y, 2) + + Math.pow(pt.z - z, 2); + } + + /** + * Get the normalized vector. + * + * @return vector + */ + public Vector normalize() { + return divide(length()); + } + + /** + * Checks to see if a vector is contained with another. + * + * @param min + * @param max + * @return + */ + public boolean containedWithin(Vector min, Vector max) { + return x >= min.getX() && x <= max.getX() + && y >= min.getY() && y <= max.getY() + && z >= min.getZ() && z <= max.getZ(); + } + + /** + * Checks to see if a vector is contained with another. + * + * @param min + * @param max + * @return + */ + public boolean containedWithinBlock(Vector min, Vector max) { + return getBlockX() >= min.getBlockX() && getBlockX() <= max.getBlockX() + && getBlockY() >= min.getBlockY() && getBlockY() <= max.getBlockY() + && getBlockZ() >= min.getBlockZ() && getBlockZ() <= max.getBlockY(); + } + + /** + * Clamp the Y component. + * + * @param min + * @param max + * @return + */ + public Vector clampY(int min, int max) { + return new Vector(x, Math.max(min, Math.min(max, y)), z); + } + + /** + * 2D transformation. + * + * @param angle in degrees + * @param aboutX + * @param aboutZ + * @param translateX + * @param translateZ + * @return + */ + public Vector transform2D(double angle, + double aboutX, double aboutZ, double translateX, double translateZ) { + angle = Math.toRadians(angle); + double x = this.x; + double z = this.z; + double x2 = x * Math.cos(angle) - z * Math.sin(angle); + double z2 = x * Math.sin(angle) + z * Math.cos(angle); + return new Vector(x2 + aboutX + translateX, + y, + z2 + aboutZ + translateZ); + } + + /** + * Get a block point from a point. + * + * @param x + * @param y + * @param z + * @return point + */ + public static Vector toBlockPoint(double x, double y, double z) { + return new Vector((int)Math.floor(x), + (int)Math.floor(y), + (int)Math.floor(z)); + } + + /** + * Get a block point from a point. + * + * @return point + */ + public BlockVector toBlockPoint() { + return new BlockVector((int)Math.floor(x), + (int)Math.floor(y), + (int)Math.floor(z)); + } + + /** + * Checks if another object is equivalent. + * + * @param obj + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector)) { + return false; + } + Vector other = (Vector)obj; + return other.getX() == this.x && other.getY() == this.y && other.getZ() == this.z; + + } + + /** + * Gets the hash code. + * + * @return hash code + */ + @Override + public int hashCode() { + return ((new Double(x)).hashCode() >> 13) ^ + ((new Double(y)).hashCode() >> 7) ^ + (new Double(z)).hashCode(); + } + + /** + * Returns string representation "(x, y, z)". + * + * @return string + */ + @Override + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + + /** + * Gets a BlockVector version. + * + * @return BlockVector + */ + public BlockVector toBlockVector() { + return new BlockVector(this); + } + + /** + * Gets the minimum components of two vectors. + * + * @param v1 + * @param v2 + * @return minimum + */ + public static Vector getMinimum(Vector v1, Vector v2) { + return new Vector( + Math.min(v1.getX(), v2.getX()), + Math.min(v1.getY(), v2.getY()), + Math.min(v1.getZ(), v2.getZ())); + } + + /** + * Gets the maximum components of two vectors. + * + * @param v1 + * @param v2 + * @return maximum + */ + public static Vector getMaximum(Vector v1, Vector v2) { + return new Vector( + Math.max(v1.getX(), v2.getX()), + Math.max(v1.getY(), v2.getY()), + Math.max(v1.getZ(), v2.getZ())); + } +} diff --git a/src/com/sk89q/wg_regions_52/Vector2D.java b/src/com/sk89q/wg_regions_52/Vector2D.java new file mode 100644 index 0000000..58ed361 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/Vector2D.java @@ -0,0 +1,191 @@ +package com.sk89q.wg_regions_52; +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +/** + * + * @author sk89q + */ +public class Vector2D { + protected final double x, z; + + /** + * Construct the Vector2D object. + * + * @param x + * @param z + */ + public Vector2D(double x, double z) { + this.x = x; + this.z = z; + } + + /** + * Construct the Vector2D object. + * + * @param x + * @param z + */ + public Vector2D(int x, int z) { + this.x = (double)x; + this.z = (double)z; + } + + /** + * Construct the Vector2D object. + * + * @param x + * @param z + */ + public Vector2D(float x, float z) { + this.x = (double)x; + this.z = (double)z; + } + + /** + * Construct the Vector2D object. + * + * @param pt + */ + public Vector2D(Vector2D pt) { + this.x = pt.x; + this.z = pt.z; + } + + /** + * Construct the Vector2D object. + */ + public Vector2D() { + this.x = 0; + this.z = 0; + } + + /** + * @return the x + */ + public double getX() { + return x; + } + + /** + * @return the x + */ + public int getBlockX() { + return (int)Math.round(x); + } + + /** + * Set X. + * + * @param x + * @return new vector + */ + public Vector2D setX(double x) { + return new Vector2D(x, z); + } + + /** + * Set X. + * + * @param x + * @return new vector + */ + public Vector2D setX(int x) { + return new Vector2D(x, z); + } + + /** + * @return the z + */ + public double getZ() { + return z; + } + + /** + * @return the z + */ + public int getBlockZ() { + return (int)Math.round(z); + } + + /** + * Set Z. + * + * @param z + * @return new vector + */ + public Vector2D setZ(double z) { + return new Vector2D(x, z); + } + + /** + * Set Z. + * + * @param z + * @return new vector + */ + public Vector2D setZ(int z) { + return new Vector2D(x, z); + } + + /** + * Gets a BlockVector version. + * + * @return BlockVector + */ + public BlockVector2D toBlockVector2D() { + return new BlockVector2D(this); + } + + /** + * Checks if another object is equivalent. + * + * @param obj + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector2D)) { + return false; + } + Vector other = (Vector)obj; + return other.x == this.x && other.z == this.z; + + } + + /** + * Gets the hash code. + * + * @return hash code + */ + @Override + public int hashCode() { + return ((new Double(x)).hashCode() >> 13) ^ + (new Double(z)).hashCode(); + } + + /** + * Returns string representation "(x, y, z)". + * + * @return string + */ + @Override + public String toString() { + return "(" + x + ", " + z + ")"; + } +} diff --git a/src/com/sk89q/wg_regions_52/databases/AbstractProtectionDatabase.java b/src/com/sk89q/wg_regions_52/databases/AbstractProtectionDatabase.java new file mode 100644 index 0000000..62e5a0f --- /dev/null +++ b/src/com/sk89q/wg_regions_52/databases/AbstractProtectionDatabase.java @@ -0,0 +1,47 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.databases; + +import java.io.IOException; +import com.sk89q.wg_regions_52.managers.RegionManager; + +public abstract class AbstractProtectionDatabase implements ProtectionDatabase { + + /** + * Load the list of regions into a region manager. + * + * @throws IOException + */ + public void load(RegionManager manager) throws IOException { + load(); + manager.setRegions(getRegions()); + } + + /** + * Save the list of regions from a region manager. + * + * @throws IOException + */ + public void save(RegionManager manager) throws IOException { + setRegions(manager.getRegions()); + save(); + } + +} diff --git a/src/com/sk89q/wg_regions_52/databases/ProtectionDatabase.java b/src/com/sk89q/wg_regions_52/databases/ProtectionDatabase.java new file mode 100644 index 0000000..1072736 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/databases/ProtectionDatabase.java @@ -0,0 +1,72 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.databases; + +import com.sk89q.wg_regions_52.managers.RegionManager; +import com.sk89q.wg_regions_52.Region; +import java.io.IOException; +import java.util.Map; + +/** + * Represents a database to read and write lists of regions from and to. + * + * @author sk89q + */ +public interface ProtectionDatabase { + /** + * Load the list of regions. The method should not modify the list returned + * by getRegions() unless the load finishes successfully. + * + * @throws IOException + */ + public void load() throws IOException; + /** + * Save the list of regions. + * + * @throws IOException + */ + public void save() throws IOException; + /** + * Load the list of regions into a region manager. + * + * @param manager + * @throws IOException + */ + public void load(RegionManager manager) throws IOException; + /** + * Save the list of regions from a region manager. + * + * @param manager + * @throws IOException + */ + public void save(RegionManager manager) throws IOException; + /** + * Get a list of regions. + * + * @return + */ + public Map getRegions(); + /** + * Set the list of regions. + * + * @param regions + */ + public void setRegions(Map regions); +} diff --git a/src/com/sk89q/wg_regions_52/databases/YAMLDatabase.java b/src/com/sk89q/wg_regions_52/databases/YAMLDatabase.java new file mode 100644 index 0000000..749a944 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/databases/YAMLDatabase.java @@ -0,0 +1,196 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.databases; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import com.sk89q.wg_regions_52.BlockVector; +import com.sk89q.wg_regions_52.BlockVector2D; +import com.sk89q.wg_regions_52.Vector; + +import com.sk89q.wg_regions_52.*; +import com.sk89q.wg_regions_52.Region.CircularInheritanceException; +import com.sk89q.wg_regions_52.util.yaml.Configuration; +import com.sk89q.wg_regions_52.util.yaml.ConfigurationNode; + + +public class YAMLDatabase extends AbstractProtectionDatabase { + + private Configuration config; + private Map regions; + + public YAMLDatabase(File file) { + config = new Configuration(file); + } + + public void load() throws IOException { + config.load(); + + final Map regionData = config.getNodes("regions"); + + // No regions are even configured + if (regionData == null) { + regions = new HashMap(); + return; + } + + final Map regs = new HashMap(); + final Map parentSets = new LinkedHashMap(); + + for (final Map.Entry entry : regionData.entrySet()) { + final String id = entry.getKey().toLowerCase().replace(".", ""); + final ConfigurationNode node = entry.getValue(); + + final String type = node.getString("type"); + Region region; + + try { + if (type == null) { + //logger.warning("Undefined region type for region '" + id + '"'); + continue; + } else if (type.equals("cuboid")) { + final Vector pt1 = checkNonNull(node.getVector("min")); + final Vector pt2 = checkNonNull(node.getVector("max")); + final BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); + final BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); + region = new CuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + final Integer minY = checkNonNull(node.getInt("min-y")); + final Integer maxY = checkNonNull(node.getInt("max-y")); + final List points = node.getBlockVector2dList("points", null); + region = new PolygonalRegion(id, points, minY, maxY); + } else if (type.equals("global")) { + region = new GlobalRegion(id); + } else { + //logger.warning("Unknown region type for region '" + id + '"'); + continue; + } + + regs.put(id, region); + + final String parentId = node.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + + final String info = node.getString("info"); + if (info != null) { + region.setInfo(info); + } + } catch (NullPointerException e) { + //logger.warning("Missing data for region '" + id + '"'); + } + } + + // Relink parents + for (final Map.Entry entry : parentSets.entrySet()) { + final Region parent = regs.get(entry.getValue()); + if (parent != null) { + try { + entry.getKey().setParent(parent); + } catch (CircularInheritanceException e) { + //logger.warning("Circular inheritance detect with '" + entry.getValue() + "' detected as a parent"); + } + } else { + //logger.warning("Unknown region parent: " + entry.getValue()); + } + } + + regions = regs; + } + + private V checkNonNull(V val) throws NullPointerException { + if (val == null) { + throw new NullPointerException(); + } + + return val; + } + + public void save() throws IOException { + config.clear(); + + for (final Map.Entry entry : regions.entrySet()) { + final Region region = entry.getValue(); + final ConfigurationNode node = config.addNode("regions." + entry.getKey()); + + if (region instanceof CuboidRegion) { + final CuboidRegion cuboid = (CuboidRegion) region; + node.setProperty("type", "cuboid"); + node.setProperty("min", cuboid.getMinimumPoint()); + node.setProperty("max", cuboid.getMaximumPoint()); + } else if (region instanceof PolygonalRegion) { + final PolygonalRegion poly = (PolygonalRegion) region; + node.setProperty("type", "poly2d"); + node.setProperty("min-y", poly.getMinimumPoint().getBlockY()); + node.setProperty("max-y", poly.getMaximumPoint().getBlockY()); + + final List> points = new ArrayList>(); + for (final BlockVector2D point : poly.getPoints()) { + final Map data = new HashMap(); + data.put("x", point.getBlockX()); + data.put("z", point.getBlockZ()); + points.add(data); + } + + node.setProperty("points", points); + } else if (region instanceof GlobalRegion) { + node.setProperty("type", "global"); + } else { + node.setProperty("type", region.getClass().getCanonicalName()); + } + + final Region parent = region.getParent(); + if (parent != null) { + node.setProperty("parent", parent.getId()); + } + if(region.getInfo() != null){ + node.setProperty("info", region.getInfo()); + } + } + + config.setHeader("#\r\n" + + "# WorldGuard regions file\r\n" + + "#\r\n" + + "# WARNING: THIS FILE IS AUTOMATICALLY GENERATED. If you modify this file by\r\n" + + "# hand, be aware that A SINGLE MISTYPED CHARACTER CAN CORRUPT THE FILE. If\r\n" + + "# WorldGuard is unable to parse the file, your regions will FAIL TO LOAD and\r\n" + + "# the contents of this file will reset. Please use a YAML validator such as\r\n" + + "# http://yaml-online-parser.appspot.com (for smaller files).\r\n" + + "#\r\n" + + "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + + "#"); + config.save(); + } + + public Map getRegions() { + return regions; + } + + public void setRegions(Map regions) { + this.regions = regions; + } + +} diff --git a/src/com/sk89q/wg_regions_52/managers/FlatRegionManager.java b/src/com/sk89q/wg_regions_52/managers/FlatRegionManager.java new file mode 100644 index 0000000..3e1d1b3 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/managers/FlatRegionManager.java @@ -0,0 +1,199 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.sk89q.wg_regions_52.managers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; +import com.sk89q.wg_regions_52.ApplicableRegionSet; +import com.sk89q.wg_regions_52.Region; +import com.sk89q.wg_regions_52.Vector; +import com.sk89q.wg_regions_52.databases.ProtectionDatabase; +import java.util.Iterator; +import org.bukkit.Location; + +/** + * A very simple implementation of the region manager that uses a flat list + * and iterates through the list to identify applicable regions. This method + * is not very efficient. + * + * @author sk89q + */ +public class FlatRegionManager extends RegionManager { + + /** + * List of regions. + */ + private Map regions; + + /** + * Construct the manager. + * + * @param regionloader + */ + public FlatRegionManager(ProtectionDatabase regionloader) { + super(regionloader); + regions = new TreeMap(); + } + + /** + * Get a list of regions. + * + * @return + */ + @Override + public Map getRegions() { + return regions; + } + + /** + * Set a list of regions. + */ + @Override + public void setRegions(Map regions) { + this.regions = new TreeMap(regions); + } + + /** + * Adds a region. + * + * @param region + */ + @Override + public void addRegion(Region region) { + regions.put(region.getId().toLowerCase(), region); + } + + /** + * Removes a region and its children. + * + * @param id + */ + @Override + public void removeRegion(String id) { + final Region region = regions.get(id.toLowerCase()); + regions.remove(id.toLowerCase()); + + if (region != null) { + final List removeRegions = new ArrayList(); + final Iterator iter = regions.values().iterator(); + while (iter.hasNext()) { + final Region curRegion = iter.next(); + if (curRegion.getParent() == region) { + removeRegions.add(curRegion.getId().toLowerCase()); + } + } + + for (final String remId : removeRegions) { + removeRegion(remId); + } + } + } + + /** + * Return whether a region exists by an ID. + * + * @param id + * @return + */ + @Override + public boolean hasRegion(String id) { + return regions.containsKey(id.toLowerCase()); + } + + public boolean hasRegion(Location loc) { + for (final Region region : regions.values()) { + if (region.contains(loc)) { + return true; + } + } + return false; + } + + /** + * Get a region by its ID. + * + * @param id + */ + @Override + public Region getRegion(String id) { + return regions.get(id.toLowerCase()); + } + + /** + * Get an object for a point for rules to be applied with. + * + * @param pt + * @return + */ + @Override + public ApplicableRegionSet getApplicableRegions(Vector pt) { + final TreeSet appRegions = + new TreeSet(); + + for (final Region region : regions.values()) { + if (region.contains(pt)) { + appRegions.add(region); + + Region parent = region.getParent(); + + while (parent != null) { + if (!appRegions.contains(parent)) { + appRegions.add(region); + } + + parent = parent.getParent(); + } + } + } + + return new ApplicableRegionSet(appRegions, regions.get("__global__")); + } + + /** + * Get a list of region IDs that contain a point. + * + * @param pt + * @return + */ + @Override + public List getApplicableRegionsIDs(Vector pt) { + final List applicable = new ArrayList(); + + for (final Map.Entry entry : regions.entrySet()) { + if (entry.getValue().contains(pt)) { + applicable.add(entry.getKey()); + } + } + + return applicable; + } + + /** + * Get the number of regions. + * + * @return + */ + @Override + public int size() { + return regions.size(); + } +} diff --git a/src/com/sk89q/wg_regions_52/managers/GlobalRegionManager.java b/src/com/sk89q/wg_regions_52/managers/GlobalRegionManager.java new file mode 100644 index 0000000..a3001a6 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/managers/GlobalRegionManager.java @@ -0,0 +1,213 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.sk89q.wg_regions_52.managers; + +import com.sk89q.wg_regions_52.databases.YAMLDatabase; +import java.io.File; +//import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; + +import me.jascotty2.bettershop.utils.BetterShopLogger; + +/** + * This class keeps track of region information for every world. It loads + * world region information as needed. + * + * @author sk89q + * @author Redecouverte + */ +public class GlobalRegionManager { + + /** + * Reference to the plugin. + */ + private Server bukkitServer; + protected File pluginFolder = null; + /** + * Map of managers per-world. + */ + private HashMap managers; + /** + * Stores the list of modification dates for the world files. This allows + * WorldGuard to reload files as needed. + */ + private HashMap lastModified; + + /** + * Construct the object. + * + * @param server reference to the bukkit server + * @param pluginFolder where the worlds folder will be saved + */ + public GlobalRegionManager(Server server, File pluginFolder) { + this.bukkitServer = server; + this.pluginFolder = pluginFolder; + managers = new HashMap(); + lastModified = new HashMap(); + + (new File(pluginFolder, "regions")).mkdirs(); + } + + /** + * Unload region information. + */ + public void unload() { + managers.clear(); + lastModified.clear(); + } + + /** + * Get the path for a world's regions file. + * + * @param name + * @return + */ + protected File getPath(String name) { + return new File(pluginFolder, "regions" + File.separator + name + ".yml"); + } + + /** + * Unload region information for a world. + * + * @param name + */ + public void unload(String name) { + final RegionManager manager = managers.get(name); + + if (manager != null) { + managers.remove(name); + lastModified.remove(name); + } + } + + /** + * Unload all region information. + */ + public void unloadAll() { + managers.clear(); + lastModified.clear(); + } + + /** + * Load region information for a world. + * + * @param world + * @return + */ + public RegionManager load(World world) { + final String name = world.getName(); + final File file = getPath(name); + RegionManager manager = null; + + try { + // Create a manager + manager = new FlatRegionManager(new YAMLDatabase(file)); + managers.put(name, manager); + if (file.exists()) { + manager.load(); + + BetterShopLogger.Log(manager.getRegions().size() + + " regions loaded for '" + name + "'"); + // Store the last modification date so we can track changes + lastModified.put(name, file.lastModified()); + } + + return manager; + //} catch (FileNotFoundException e) { + } catch (IOException e) { + BetterShopLogger.Log(Level.WARNING, "Failed to load regions from file " + + file.getAbsolutePath() + " : " + e.getMessage()); + } + + return manager; + } + + /** + * Preloads region managers for all worlds. + */ + public void preload() { + // Load regions + for (final World world : bukkitServer.getWorlds()) { + load(world); + } + } + + /** + * Reloads the region information from file when region databases + * have changed. + */ + public void reloadChanged() { + for (final String name : managers.keySet()) { + final File file = getPath(name); + + Long oldDate = lastModified.get(name); + + if (oldDate == null) { + oldDate = 0L; + } + + try { + if (file.lastModified() > oldDate) { + final World world = bukkitServer.getWorld(name); + + if (world != null) { + load(world); + } + } + } catch (Exception e) { + } + } + } + + /** + * Get the region manager for a particular world. + * + * @param world + * @return + */ + public RegionManager get(World world) { + RegionManager manager = managers.get(world.getName()); + if (manager == null) { + manager = load(world); + } + return manager; + } + + public Collection getAll() { + return managers.values(); + } + + public Set> getAllEntries() { + return managers.entrySet(); + } + + public boolean hasRegion(Location loc) { + final World world = loc.getWorld(); + final RegionManager mgr = get(world); + return mgr != null && mgr.getApplicableRegions(loc).size() > 0; + } +} diff --git a/src/com/sk89q/wg_regions_52/managers/RegionManager.java b/src/com/sk89q/wg_regions_52/managers/RegionManager.java new file mode 100644 index 0000000..7e8a768 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/managers/RegionManager.java @@ -0,0 +1,153 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.wg_regions_52.managers; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import com.sk89q.wg_regions_52.databases.ProtectionDatabase; +import com.sk89q.wg_regions_52.Region; +import com.sk89q.wg_regions_52.ApplicableRegionSet; +import com.sk89q.wg_regions_52.BukkitUtil; +import com.sk89q.wg_regions_52.Vector; + +/** + * An abstract class for getting, setting, and looking up regions. The most + * simple implementation uses a flat list and iterates through the entire list + * to look for applicable regions, but a more complicated (and more efficient) + * implementation may use space partitioning techniques. + * + * @author sk89q + */ +public abstract class RegionManager { + + ProtectionDatabase loader; + + /** + * Construct the object. + * + * @param loader + */ + public RegionManager(ProtectionDatabase loader) { + this.loader = loader; + } + + /** + * Load the list of regions. If the regions do not load properly, then + * the existing list should be used (as stored previously). + * + * @throws IOException thrown on load error + */ + public void load() throws IOException { + loader.load(this); + } + + /** + * Save the list of regions. + * + * @throws IOException thrown on save eIf checking multiple flags for a single locationror + */ + public void save() throws IOException { + loader.save(this); + } + + /** + * Get a map of regions. Use one of the region manager methods + * if possible if working with regions. + * + * @return map of regions, with keys being region IDs (lowercase) + */ + public abstract Map getRegions(); + + /** + * Set a list of regions. Keys should be lowercase in the given + * map fo regions. + * + * @param regions map of regions + */ + public abstract void setRegions(Map regions); + + /** + * Adds a region. If a region by the given name already exists, then + * the existing region will be replaced. + * + * @param region region to add + */ + public abstract void addRegion(Region region); + + /** + * Return whether a region exists by an ID. + * + * @param id id of the region, can be mixed-case + * @return whether the region exists + */ + public abstract boolean hasRegion(String id); + + /** + * Get a region by its ID. + * + * @param id id of the region, can be mixed-case + * @return region or null if it doesn't exist + */ + public abstract Region getRegion(String id); + + /** + * Removes a region, including inheriting children. + * + * @param id id of the region, can be mixed-case + */ + public abstract void removeRegion(String id); + + /** + * Get an object for a point for rules to be applied with. Use this in order + * to query for flag data or membership data for a given point. + * + * @param loc Bukkit location + * @return applicable region set + */ + public ApplicableRegionSet getApplicableRegions(org.bukkit.Location loc) { + return getApplicableRegions(BukkitUtil.toVector(loc)); + } + + /** + * Get an object for a point for rules to be applied with. Use this in order + * to query for flag data or membership data for a given point. + * + * @param pt point + * @return applicable region set + */ + public abstract ApplicableRegionSet getApplicableRegions(Vector pt); + + /** + * Get a list of region IDs that contain a point. + * + * @param pt point + * @return list of region Ids + */ + public abstract List getApplicableRegionsIDs(Vector pt); + + /** + * Get the number of regions. + * + * @return number of regions + */ + public abstract int size(); + +} diff --git a/src/com/sk89q/wg_regions_52/util/yaml/Configuration.java b/src/com/sk89q/wg_regions_52/util/yaml/Configuration.java new file mode 100644 index 0000000..ec1dd89 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/util/yaml/Configuration.java @@ -0,0 +1,204 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.util.yaml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.reader.UnicodeReader; +import org.yaml.snakeyaml.representer.Representer; + +/** + * YAML configuration loader. To use this class, construct it with path to + * a file and call its load() method. For specifying node paths in the + * various get*() methods, they support SK's path notation, allowing you to + * select child nodes by delimiting node names with periods. + * + *

+ * For example, given the following configuration file:

+ * + *
members:
+ *     - Hollie
+ *     - Jason
+ *     - Bobo
+ *     - Aya
+ *     - Tetsu
+ * worldguard:
+ *     fire:
+ *         spread: false
+ *         blocks: [cloth, rock, glass]
+ * sturmeh:
+ *     cool: false
+ *     eats:
+ *         babies: true
+ * + *

Calling code could access sturmeh's baby eating state by using + * getBoolean("sturmeh.eats.babies", false). For lists, there are + * methods such as getStringList that will return a type safe list. + * + *

This class is currently incomplete. It is not yet possible to get a node. + *

+ * + * @author sk89q + */ +public class Configuration extends ConfigurationNode { + private Yaml yaml; + private File file; + private String header = null; + + public Configuration(File file) { + super(new HashMap()); + + final DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO); + + yaml = new Yaml(new SafeConstructor(), new Representer(), options); + + this.file = file; + } + + /** + * Loads the configuration file. + * + * @throws IOException + */ + public void load() throws IOException { + FileInputStream stream = null; + + try { + stream = new FileInputStream(file); + read(yaml.load(new UnicodeReader(stream))); + } catch (ConfigurationException e) { + root = new HashMap(); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + } + } + } + + /** + * Set the header for the file as a series of lines that are terminated + * by a new line sequence. + * + * @param headerLines header lines to prepend + */ + public void setHeader(String... headerLines) { + final StringBuilder header = new StringBuilder(); + + for (final String line : headerLines) { + if (header.length() > 0) { + header.append("\r\n"); + } + header.append(line); + } + + setHeader(header.toString()); + } + + /** + * Set the header for the file. A header can be provided to prepend the + * YAML data output on configuration save. The header is + * printed raw and so must be manually commented if used. A new line will + * be appended after the header, however, if a header is provided. + * + * @param header header to prepend + */ + public void setHeader(String header) { + this.header = header; + } + + /** + * Return the set header. + * + * @return + */ + public String getHeader() { + return header; + } + + /** + * Saves the configuration to disk. All errors are clobbered. + * + * @return true if it was successful + */ + public boolean save() { + FileOutputStream stream = null; + + final File parent = file.getParentFile(); + + if (parent != null) { + parent.mkdirs(); + } + + try { + stream = new FileOutputStream(file); + final OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8"); + if (header != null) { + writer.append(header); + writer.append("\r\n"); + } + yaml.dump(root, writer); + return true; + } catch (IOException e) {} finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) {} + } + + return false; + } + + @SuppressWarnings("unchecked") + private void read(Object input) throws ConfigurationException { + try { + if ( null == input ) { + root = new HashMap(); + } else { + root = (Map)input; + } + } catch (ClassCastException e) { + throw new ConfigurationException("Root document must be an key-value structure"); + } + } + + /** + * This method returns an empty ConfigurationNode for using as a + * default in methods that select a node from a node list. + * @return + */ + public static ConfigurationNode getEmptyNode() { + return new ConfigurationNode(new HashMap()); + } +} diff --git a/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationException.java b/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationException.java new file mode 100644 index 0000000..8f7ed10 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationException.java @@ -0,0 +1,37 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.util.yaml; + +/** + * Configuration exception. + * + * @author sk89q + */ +public class ConfigurationException extends Exception { + private static final long serialVersionUID = -2442886939908724203L; + + public ConfigurationException() { + super(); + } + + public ConfigurationException(String msg) { + super(msg); + } +} diff --git a/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationNode.java b/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationNode.java new file mode 100644 index 0000000..6038e21 --- /dev/null +++ b/src/com/sk89q/wg_regions_52/util/yaml/ConfigurationNode.java @@ -0,0 +1,789 @@ +// $Id$ +/* + * WorldGuard + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.wg_regions_52.util.yaml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.sk89q.wg_regions_52.BlockVector2D; +import com.sk89q.wg_regions_52.Vector; +import com.sk89q.wg_regions_52.Vector2D; + +/** + * Represents a configuration node. + * + * @author sk89q + */ +public class ConfigurationNode { + protected Map root; + + protected ConfigurationNode(Map root) { + this.root = root; + } + + /** + * Clear all nodes. + */ + public void clear() { + root.clear(); + } + + /** + * Gets a property at a location. This will either return an Object + * or null, with null meaning that no configuration value exists at + * that location. This could potentially return a default value (not yet + * implemented) as defined by a plugin, if this is a plugin-tied + * configuration. + * + * @param path path to node (dot notation) + * @return object or null + */ + @SuppressWarnings("unchecked") + public Object getProperty(String path) { + if (!path.contains(".")) { + final Object val = root.get(path); + if (val == null) { + return null; + } + return val; + } + + final String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + final Object o = node.get(parts[i]); + + if (o == null) { + return null; + } + + if (i == parts.length - 1) { + return o; + } + + try { + node = (Map)o; + } catch (ClassCastException e) { + return null; + } + } + + return null; + } + + /** + * Prepare a value for serialization, in case it's not a native type + * (and we don't want to serialize objects as YAML objects). + * + * @param value + * @return + */ + private Object prepareSerialization(Object value) { + if (value instanceof Vector) { + final Map out = new HashMap(); + final Vector vec = (Vector) value; + out.put("x", vec.getX()); + out.put("y", vec.getY()); + out.put("z", vec.getZ()); + return out; + } + + return value; + } + + /** + * Set the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path + * @param value + */ + @SuppressWarnings("unchecked") + public void setProperty(String path, Object value) { + value = prepareSerialization(value); + + if (!path.contains(".")) { + root.put(path, value); + return; + } + + final String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.put(parts[i], value); + return; + } + + if (o == null || !(o instanceof Map)) { + // This will override existing configuration data! + o = new HashMap(); + node.put(parts[i], o); + } + + node = (Map)o; + } + } + + /** + * Adds a new node to the given path. The returned object is a reference + * to the new node. This method will replace an existing node at + * the same path. See setProperty. + * + * @param path + * @return + */ + public ConfigurationNode addNode(String path) { + final Map map = new HashMap(); + final ConfigurationNode node = new ConfigurationNode(map); + setProperty(path, map); + return node; + } + + /** + * Gets a string at a location. This will either return an String + * or null, with null meaning that no configuration value exists at + * that location. If the object at the particular location is not actually + * a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or null + */ + public String getString(String path) { + final Object o = getProperty(path); + if (o == null) { + return null; + } + return o.toString(); + } + + /** + * Gets a vector at a location. This will either return an Vector + * or a null. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or default + */ + public Vector getVector(String path) { + final ConfigurationNode o = getNode(path); + if (o == null) { + return null; + } + + Double x = o.getDouble("x"); + Double y = o.getDouble("y"); + Double z = o.getDouble("z"); + + if (x == null || y == null || z == null) { + return null; + } + + return new Vector(x, y, z); + } + + /** + * Gets a 2D vector at a location. This will either return an Vector + * or a null. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or default + */ + public Vector2D getVector2d(String path) { + final ConfigurationNode o = getNode(path); + if (o == null) { + return null; + } + + Double x = o.getDouble("x"); + Double z = o.getDouble("z"); + + if (x == null || z == null) { + return null; + } + + return new Vector2D(x, z); + } + + /** + * Gets a string at a location. This will either return an Vector + * or the default value. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public Vector getVector(String path, Vector def) { + final Vector v = getVector(path); + if (v == null) { + setProperty(path, def); + return def; + } + return v; + } + + /** + * Gets a string at a location. This will either return an String + * or the default value. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public String getString(String path, String def) { + final String o = getString(path); + if (o == null) { + setProperty(path, def); + return def; + } + return o; + } + + /** + * Gets an integer at a location. This will either return an integer + * or null. If the object at the particular location is not + * actually a integer, the default value will be returned. However, other + * number types will be casted to an integer. + * + * @param path path to node (dot notation) + * @return integer or null + */ + public Integer getInt(String path) { + final Integer o = castInt(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets an integer at a location. This will either return an integer + * or the default value. If the object at the particular location is not + * actually a integer, the default value will be returned. However, other + * number types will be casted to an integer. + * + * @param path path to node (dot notation) + * @param def default value + * @return int or default + */ + public int getInt(String path, int def) { + final Integer o = castInt(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a double at a location. This will either return an double + * or null. If the object at the particular location is not + * actually a double, the default value will be returned. However, other + * number types will be casted to an double. + * + * @param path path to node (dot notation) + * @return double or null + */ + public Double getDouble(String path) { + final Double o = castDouble(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets a double at a location. This will either return an double + * or the default value. If the object at the particular location is not + * actually a double, the default value will be returned. However, other + * number types will be casted to an double. + * + * @param path path to node (dot notation) + * @param def default value + * @return double or default + */ + public double getDouble(String path, double def) { + final Double o = castDouble(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a boolean at a location. This will either return an boolean + * or null. If the object at the particular location is not + * actually a boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @return boolean or null + */ + public Boolean getBoolean(String path) { + final Boolean o = castBoolean(getProperty(path)); + if (o == null) { + return null; + } else { + return o; + } + } + + /** + * Gets a boolean at a location. This will either return an boolean + * or the default value. If the object at the particular location is not + * actually a boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @param def default value + * @return boolean or default + */ + public boolean getBoolean(String path, boolean def) { + final Boolean o = castBoolean(getProperty(path)); + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Get a list of keys at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return list of keys + */ + @SuppressWarnings("unchecked") + public List getKeys(String path) { + if (path == null) return new ArrayList(root.keySet()); + final Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + return new ArrayList(((Map)o).keySet()); + } else { + return null; + } + } + + /** + * Gets a list of objects at a location. If the list is not defined, + * null will be returned. The node must be an actual list. + * + * @param path path to node (dot notation) + * @return boolean or default + */ + @SuppressWarnings("unchecked") + public List getList(String path) { + final Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof List) { + return (List)o; + } else { + return null; + } + } + + /** + * Gets a list of strings. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. If an item in the list + * is not a string, it will be converted to a string. The node must be + * an actual list and not just a string. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of strings + */ + public List getStringList(String path, List def) { + final List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + final List list = new ArrayList(); + for (final Object o : raw) { + if (o == null) { + continue; + } + + list.add(o.toString()); + } + + return list; + } + + /** + * Gets a list of integers. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and not just an integer. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getIntList(String path, List def) { + final List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + final List list = new ArrayList(); + for (final Object o : raw) { + final Integer i = castInt(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of doubles. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a double. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getDoubleList(String path, List def) { + final List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + final List list = new ArrayList(); + for (final Object o : raw) { + final Double i = castDouble(o); + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of booleans. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBooleanList(String path, List def) { + final List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + final List list = new ArrayList(); + for (final Object o : raw) { + final Boolean tetsu = castBoolean(o); + if (tetsu != null) { + list.add(tetsu); + } + } + + return list; + } + + /** + * Gets a list of vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getVectorList(String path, List def) { + + final List raw = getNodeList(path, null); + final List list = new ArrayList(); + + for (final ConfigurationNode o : raw) { + final Double x = o.getDouble("x"); + final Double y = o.getDouble("y"); + final Double z = o.getDouble("z"); + + if (x == null || y == null || z == null) { + continue; + } + + list.add(new Vector(x, y, z)); + } + + return list; + } + + /** + * Gets a list of 2D vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getVector2dList(String path, List def) { + + final List raw = getNodeList(path, null); + final List list = new ArrayList(); + + for (final ConfigurationNode o : raw) { + final Double x = o.getDouble("x"); + final Double z = o.getDouble("z"); + + if (x == null || z == null) { + continue; + } + + list.add(new Vector2D(x, z)); + } + + return list; + } + + /** + * Gets a list of 2D vectors. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a vector, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBlockVector2dList(String path, List def) { + + final List raw = getNodeList(path, null); + final List list = new ArrayList(); + + for (final ConfigurationNode o : raw) { + final Double x = o.getDouble("x"); + final Double z = o.getDouble("z"); + + if (x == null || z == null) { + continue; + } + + list.add(new BlockVector2D(x, z)); + } + + return list; + } + + /** + * Gets a list of nodes. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + @SuppressWarnings("unchecked") + public List getNodeList(String path, List def) { + final List raw = getList(path); + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + final List list = new ArrayList(); + for (final Object o : raw) { + if (o instanceof Map) { + list.add(new ConfigurationNode((Map)o)); + } + } + + return list; + } + + /** + * Get a configuration node at a path. If the node doesn't exist or the + * path does not lead to a node, null will be returned. A node has + * key/value mappings. + * + * @param path + * @return node or null + */ + @SuppressWarnings("unchecked") + public ConfigurationNode getNode(String path) { + final Object raw = getProperty(path); + if (raw instanceof Map) { + return new ConfigurationNode((Map)raw); + } + + return null; + } + + /** + * Get a list of nodes at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return map of nodes + */ + @SuppressWarnings("unchecked") + public Map getNodes(String path) { + final Object o = getProperty(path); + if (o == null) { + return null; + } else if (o instanceof Map) { + final Map nodes = new HashMap(); + + for (final Map.Entry entry : ((Map)o).entrySet()) { + if (entry.getValue() instanceof Map) { + nodes.put(entry.getKey(), new ConfigurationNode((Map) entry.getValue())); + } + } + + return nodes; + } else { + return null; + } + } + + /** + * Casts a value to an integer. May return null. + * + * @param o + * @return + */ + private static Integer castInt(Object o) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (int)(Byte)o; + } else if (o instanceof Integer) { + return (Integer)o; + } else if (o instanceof Double) { + return (int)(double)(Double)o; + } else if (o instanceof Float) { + return (int)(float)(Float)o; + } else if (o instanceof Long) { + return (int)(long)(Long)o; + } else { + return null; + } + } + + /** + * Casts a value to a double. May return null. + * + * @param o + * @return + */ + private static Double castDouble(Object o) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (double)(Float)o; + } else if (o instanceof Double) { + return (Double)o; + } else if (o instanceof Byte) { + return (double)(Byte)o; + } else if (o instanceof Integer) { + return (double)(Integer)o; + } else if (o instanceof Long) { + return (double)(Long)o; + } else { + return null; + } + } + + /** + * Casts a value to a boolean. May return null. + * + * @param o + * @return + */ + private static Boolean castBoolean(Object o) { + if (o == null) { + return null; + } else if (o instanceof Boolean) { + return (Boolean)o; + } else { + return null; + } + } + + /** + * Remove the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path + */ + @SuppressWarnings("unchecked") + public void removeProperty(String path) { + if (!path.contains(".")) { + root.remove(path); + return; + } + + final String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + final Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.remove(parts[i]); + return; + } + + node = (Map)o; + } + } +} \ No newline at end of file diff --git a/src/config.yml b/src/config.yml new file mode 100644 index 0000000..d98bae1 --- /dev/null +++ b/src/config.yml @@ -0,0 +1,537 @@ +## BetterShop Configuration file ## +################################### + +errors: + # at start, will tell you if this plugin is the current version + CheckForUpdates: true + # should updates be downloaded automatically? (note: deletes the old version) + AutoUpdate: false + # as an attempt to stay on top of any bugs, error reports can be sent to notify me of a potential bug + # (all information is confidential, and no private information is sent) + # an error id is generated & outputted to the log, and can be viewed at ftp://nas.boldlygoingnowhere.org/youridhere + #note: this helps, but is not self-explanitory when trying to track down a bug + # if you're having problems, please post at http://forums.bukkit.org/threads/3359/ + # with the id & the steps you used to get there + AutoErrorReporting: true + # share your server ip (unhashed) with the devs? + # (if true, means you don't mind us coming over for a visit) + UnMaskErrorID: false + # if you want to send a custom message with your error report + # ex: email for me to contact you for more info about an error + # info about your server (installed plugins?) + # note: message cannot be longer than 90 characters + # (this is not displayed to the user or in the logs, it is sent in the error report) + CustomErrorMessage: "" + + #if you're using jascotty2's MinecraftIM plugin, should errors be sent to you? + sendLogOnError: true + #send everything logged? (not just errors & exceptions) + sendAllLog: false + +shop: + # number of lines outputted in shop list + ItemsPerPage: 9 + # broadcast all transactions publicly? + publicmarket: false + + # if all commands should be logged + logcommands: false + # if logging commands, where to save + commandLogFile: Commands.log + # the format used when logging commands (if enabled) + # 24-hour format hour + # 12-hour format hour + # minute + # second + # epoch time (seconds since 1 January 1970 0:00:00 UTC) + # am/pm + # timezone (eg. GMT) + # RFC 822 time zone (eg. -0800) + # tab character + # Year (1996) + # Month in year (July -> 07) + # Week in year + # Day in year + # Day in month + # Day in week (Tue) + # username + # bettershop command executed + logformat: "/ :: > " + + #item buying behavior + #if someone without BetterShop.admin.illegal can buy illegal items + allowbuyillegal: true + #whether maxstack should be honored + # if false, all items can be purchased stacked to 64 + usemaxstack: true + #used tools can be bought back? + buybacktools: true + #should the shop buy items from users? + buybackenabled: true + #if there are LivingEntities for sale, what's the max that can be bought at once? + # (does not limit total, if buy repeatedly) + maxEntityPurchase: 3 + + # sign based shops enabled? + signShops: true + # color of an active sign's ttle + activeSignColor: blue + # color the names of items on signs? + signItemColor: false + # swap black & white item colors? + signItemColorBWswap: false + # don't allow signs to be destroyed by unauthorized players? + signDestroyProtection: true + # scan for removed signs? (mainly WorldEdit) + weSignDestroyProtection: false + # scan for destroyed signs? + tntSignDestroyProtection: true + + # chest based shops enabled? + chestShops: true + # don't allow chest to be destroyed by unauthorized players? + chestDestroyProtection: true + # protect chests from being destroyed by tnt? + tntChestDestroyProtection: true + # custom chest name + # if the user is editing the chest + # note: as of mc 1.8, chests cannot be more than 16 characters long :( + # so the plugin will automattically trim to 16 chars + chestText: "BetterShop Chest Shop " + # how the edit str is displayed + chestEditText: "(Editing)" + # if an extra (empty) bar should be added to the bottom of a chest shop + # this can make it possible to sell to a full chest + chestSellBar: false + + # method for the discount groups when selling + # options: + # Lower: buy & sell prices lowered by the same % (default) + # None: sell prices are not affected + # Higher: sell prices INCREASED by percentage (not recommended) + # when using None or Higher: beware of instances where buy could be lower than sell + # - if this happens, a user could exploit the loophole & gain free money + sellDiscountMethod: Lower + + # command-based shop mode + # modes: GLOBAL, REGIONS, BOTH, NONE (default: global) + # GLOBAL: regions are areas where command-based shop access is disabled + # REGIONS: globally disabled, except for within regions + # BOTH: allows command-based shop access from anywhere + # NONE: cannot use commands (or spout) to use the shop + commandShop: global + #items listed here will be sorted before everything else, in this order + # ex: 4, 35, 35:1, 35:2, 35:3, 35:4, 35:5, 8, 2 + # (can also use item names) + customsort: + + #what color to reset text to after printing an item with a custom color + # black, dark blue, dark green, dark sky blue, red, magenta, gold or amber, light grey + # dark grey, medium blue, light green, cyan, orange-red, pink, yellow, white + defaultItemColor: white + + # what to use for table/file name (files are saved as .csv) + tablename: BetterShop + + #if using Help: should main commands not be listed on the main page? + hideHelp: false + + # if using BOSEconomy, can set what bank will buy/sell items (optional) + BOSBank: + + # default currency name (not yet used, but might be if no economy plugin found) + currencyName: "Coin" + + # what should be used for economy + # auto: uses whatever econ plugin is found (iConomy 4,5,6, BOSEcon 6,7, MultiCurrency, EssentialsEco) + # exp: use experience as currency (does not change when enchanting) + # total: total experience (what is used when enchanting) + # ----------------- note: TOTAL is an integer value: cannot have decimals in prices + economy: auto + + +spout: + # if you have spout, whether to use the spout gui + enabled: true + + # what key to bring up the display + key: b + + # if the menu listing should use names for buttons + # (if false, will just be clickable images) + largeMenu: true + + # for the menu listing, if should use whole pages + # (if false, each page scrolls the list over by 1) + usePages: true + + # if you want to use categories, how to use them + # options: + # tab / tabbed : categories use top row of listing on the screen + # cycle : button above the exit button to cycle through categories + # none (default) : no category selection + categories: none + + #if using categories, is should use the orger defined in itemsdb.yml + useSort: true + +discountGroups: + # groups are defined using permissions + # BetterShop.discount.(NAME) + # ex: BetterShop.discount.VIP + # note: "BetterShop.discount.none" negates discounts + # discounts are defined in whole-number percentages + # note: values greater than 100% are truncated to 100% + # (so that users aren't given money to buy items) + # also, negative values can be used to force users to pay more + # (buy & sell prices are changed by the same amount) + VIP: 10 + +MySQL: + #use MySQL for databases? (includes pricelist, marketActivity, transactionTotals, and itemStock) + useMySQL: false + + # database to use in MySQL + database: minecraft + # MySQL Login Username + username: root + # MySQL Login Password + password: root + # MySQL Connection Hostname (IP) + Hostname: localhost + # MySQL Connection Port Number + Port: 3306 + # Cache pricelist table? + cache: true + # max lifespan of a list cache + # positive integer values only, units being s(econds) m(inutes) h(hours) d(ays) w(eeks) M(onths) (default: h) + # 0 = never update cache (use if you won't be editing the database outside of the plugin) + # if you don't want to have caching, you shoud still enable it & set this to something low, like 25 seconds + # note: this also affects item stock caching, if enabled + cacheUpdate: 5m + # if cache is disabled, will hold a temp cache of the last item looked up + # (to lower database lookups & increase performance) + # use this to change how long it is considered current (seconds) + tempCacheTTL: 10 + +# section for logging transactions (future can be used to make a dynamic market) +transactionLog: + enabled: false + # what to use for transaction log tablename (or flatfile flename) + logtablename: BetterShopMarketActivity + # max lifespan of an individual record + # positive integer values only, units being m(inutes) h(hours) d(ays) w(eeks) M(onths) (default: h) + # recommend not going too high, since the db could get pretty large + # (older records are removed when adding new ones) + userTansactionLifespan: 2d + +# besides logging individual transactions, can also record the totals for whole world +totalsTransactionLog: + enabled: false + logtablename: BetterShopTransactionTotals + +dynamicMarket: + # dynamic pricing not yet implemented.. + # when it is, will require transactionLog.enabled: true + dynamicPricing: false + + # the following are not influenced if dynamicPricing is disabled + + #if a price is not set on a craftable item, can still sell if the materials to make are for sale + sellcraftables: true + + # if sellcraftables, how much % more a crafted item costs than the materials + # (sell prices also reduced by this amount) + sellcraftableMarkup: 5 + + # if sellcraftables, if colored wool should sell for less than sellcraftableMarkup + # (otherwise, a player could buy dye, color a sheep, get 1-3 colored wool, make profit) + woolsellweight: true + +itemStock: + # enable a stock for items? + enabled: false + # table/file name to use + tablename: BetterShopItemStock + # how much an added item has to start with + startStock: 200 + # max stock to carry (stock is increased with sales) + #(cannot exceed 2^63-1, or 9,223,372,036,854,775,807) + maxStock: 500 + # deny sales if stock is full? + # (if false, stock will not increase above maxStock.. it will just be thrown out) + noOverStock: true + # restock interval: automatic (on stock check, not timer), and stock will be reset to startStock value + # positive integer values only, units being m(inutes) h(hours) d(ays) w(eeks) M(onths) (default: h) + # 0 == never restock + # note: does not save the last update time, so a server restart could interfere with this cycle + restock: 6h + +########################################## +# strings section: Text used in messages # +########################################## +strings: + +# In some strings below, you can use certain keys to represent +# dynamic fields that are filled in by the plugin. These keys are +# always in angle brackets "< >". I've tried to use as many of the +# allowable tags in each string as I can. Those that I couldn't use +# but are still valid are listed in the description following the +# string. +# +# For example, in the notforsale string, is used to +# represent the item that the user is trying to buy but can't. +# whenever using , will output using a custom color is one is set for the item, +# so it's a good idea to put a color formatting afterwards +# In this file the default for that line is: +# +# notforsale: "&f[&f] &2is not for sale." +# +# In game, trying to buy wood while it's not in the shop would result +# in seeing this message: +# +# SHOP: [wood] is not for sale. +# +### +# +# Colors are specified by using "&[colorcode]". Never put colors within +# the angle brackets of a tag! +# Color table: +# &0 is black +# &1 is dark blue +# &2 is dark green +# &3 is dark sky blue +# &4 is red +# &5 is magenta +# &6 is gold or amber +# &7 is light grey +# &8 is dark grey +# &9 is medium blue +# &a is light green +# &b is cyan +# &c is orange-red +# &d is pink +# &e is yellow +# &f is white +# +# note on : if you have custom item colors (there are some included) +# you should have a default color after lising the item +# Get it? Good. Now have fun. + + + +# +# general messages +#### + + prefix: "&fSHOP: &2" + # prefix is what comes before each and EVERY BetterShop message. Putting default colors here makes things easy. + + permdeny: "OI! You don't have permission to do that!" + # permdeny is the permission denied message. The key can be used to show the permission node that was checked. + + unkitem: "What is &f&2?" + # unkitem is the unknown item message. + + nicetry: "...Nice try!" + # nicetry is shown when someone tries to beat the system. Like /shopbuy diamond 0. + +# +# shopadd messages +#### + + # paramerror indicates a syntax error when trying to add an item to the shop. + paramerror: "Oops... something wasn't right there." + + # confirms a successful addition to the shop. + # - what was added + # - current buy cost + # - current sell value + # - iConomy currency + # - formatted buy cost (like "1,800 Coins" instead of "1800 Coin") + # - formatted sell value + addmsg: "&f[&f]&2 added to the shop. Buy: &f&2 Sell: &f" + + #when item is changed + # - what was changed + # - updated buy cost + # - updated sell value + # - iConomy currency + # - formatted buy cost + # - formatted sell value + chgmsg: "&f[&f]&2 updated. Buy: &f&2 Sell: &f" +# +# shopremove messages +#### + + # confirms a successful removal from the shop. + # - what was removed + removemsg: "&f[&f]&2 removed from the shop" + +# +# shopcheck messages +#### + + # pricecheck is what shows up when a player asks for a item lookup + # - what was looked up + # - current buy cost + # - current sell value + # - iConomy currency + # - how much they can buy + # - formatted buy cost (like "1,800 Coins" instead of "1800 Coin") + # - formatted sell value + # - current avaliable stock (if enabled) + pricecheck: "Price check! &f[&f]&2 Buy: &f &2Sell: &f" + #pricecheck: "Price check! &f[&f]&2 Buy: &f &2Sell: &f (stock: )" + + # when amount is specified + # - how many items looked up + multipricecheck: "Price check! &f[&f]&2 Buy: &f &2Sell: &f" + # for signs (note: doesn't work, and can't display buyprice/buycur in the sell string) + multipricechecksell: "Price check! &f[&f]&2 Sell: &f" + + multipricecheckbuy: "Price check! &f[&f]&2 Buy: &f" + + # notice: setting something like "Sell: " could output "Sell: No Dollars" if selling disabled + + # message that the item is neither for sale nor can be sold. + # - what user is looking up + nolisting: "&f[&f] &2cannot be bought or sold." + +# +# shoplist messages +#### + + # top of a shop listing page (blank for none (not recommended)) + # - current list page + # - total # of pages + listhead: "-------- Price-List Page: &f &2of &f &2--------" + + # price listing message + # - what was looked up + # - current buy cost + # - current sell value + # - iConomy currency + # - current avaliable stock (if enabled) + # using will attempt to right-align preceeding text + # using will attempt to left-align preceeding text + # using will attempt to center (all) preceeding text + # , , & will accept replacement chars: will right-align using . as spacer + listing: "&f[&f]&2 Buy: &f&2 Sell: &f" + #listing: "&f[&f]&2 Buy: &f&2 Sell: &f (stock: )" + + # bottom of market list page (blank for none) + listtail: "-----------------------------------------" + + # what is shown if a user looks up an item's aliases + # - what was looked up + # - comma-delimited string of aliases + listalias: "&f[&f]&2 is also known as: &b" + +# +# messages related to stock +#### + + #when an item cannot be bought because it is out of stock + # what tried to buy + outofstock: "This item is currently out of stock" + + # when trying to buy more than is in stock (will then buy that much) + # what tried to buy + # how much is left in stock + lowstock: "Only &favaliable for purchase" + + # when trying to sell, but there is no more room in stock (and noOverStock) + # what tried to sell + maxstock: "This item is currently at max stock" + + # when trying to sell, but there is not enough room in stock for all (and noOverStock) + # what tried to sell + # how much is in stock + highstock: "Only can be sold back" +# +# shopbuy messages +#### + + # the message shown when successfully buying + # - how much bought + # - what was bought + # - current buy cost + # - total paid + # - iConomy currency + # - formatted total (like "1,800 Coins" instead of "1800 Coin") + buymsg: "Buying &f &2&2 at &f each... &f total!" + + #public message is like buymsg, but shown to everyone (if enabled) + # - who bought from the store + # - how much bought + # - what was bought + # - current buy cost + # - total paid + # - iConomy currency + # - formatted total (like "1,800 Coins" instead of "1800 Coin") + publicbuymsg: " bought &f &2&2 for &f" + + + # outofroom is displayed when someone tries to buy more items than they can carry. + # - how much tried to buy + # - what tried to buy + # - how much can't fit into inventory + # - how much they can buy + # (will then continue to buy ) + outofroom: "You tried to buy &f&2 too many.. you can only hold &2 more" + + # when someone tries to buy more than they can afford. + # - how much tried to buy + # - what tried to buy + # - current buy cost + # - total owed + # - how much they can buy + # - iConomy currency + # - formatted total (like "1,800 Coins" instead of "1800 Coin") + insuffunds: "&4Insufficient Funds! &f &2 costs &c!" + + # when tries to sell something the shop won't buy + # - what tried to buy + notforsale: "&f[&f] &2cannot be bought" + + # if the item is not legal, and not given permissions to buy it + illegalbuy: "&4you don't have permissions to buy &f[&f]" +# +# shopsell messages +#### + + # when the user tries to sell more than they have. + # - what tried to sell + # - how much user has + # - how much user tried to sell + # (after showing message, will then sell ) + donthave: "You only have , not " + + # when the user tries to sell something that's not for sale + # - what tried to sell + donotwant: "&f[&f] &2has no value to me. No thanks." + + # Successful sale + # - how much sold + # - what was sold + # - sell value per item + # - total recieved + # - iConomy currency + # - formatted total (like "1,800 Coins" instead of "1800 Coin") + sellmsg: "Selling &f &2&2 at &f each... &f total!" + + #public message is like sellmsg, but shown to everyone (if enabled) + # - who bought from the store + # - how much bought + # - what was bought + # - current buy cost + # - total paid + # - iConomy currency + # - formatted total (like "1,800 Coins" instead of "1800 Coin") + publicsellmsg: " sold &f &2&2 for &f" + +### messages related to region shops + + # when tries a command (or spout gui) from a disabled region + regionShopDisabled: "You cannot access the store from here!" diff --git a/src/items.db b/src/items.db deleted file mode 100644 index b87fed4..0000000 --- a/src/items.db +++ /dev/null @@ -1,518 +0,0 @@ -air:0 -stone:1 -grass:2 -dirt:3 -cobblestone:4 -cobble:4 -wood:5 -plank:5 -sapling:6 -bedrock:7 -adminium:7 -water:8 -stationarywater:9 -swater:9 -lava:10 -stationarylava:11 -slava:11 -sand:12 -gravel:13 -goldore:14 -gore:14 -ironore:15 -iore:15 -coalore:16 -core:16 -log:17 -trunk:17 -redwood:17:1 -birch:17:2 -leaves:18 -sponge:19 -glass:20 -lapislazuliore:21 -lapisore:21 -lore:21 -lapislazuliblock:22 -lapisblock:22 -lblock:22 -dispenser:23 -sandstone:24 -sstone:24 -noteblock:25 -musicblock:25 -nblock:25 -mblock:25 -cloth:35 -whitecloth:35 -whitewool:35 -wool:35 -orangecloth:35:1 -magentacloth:35:2 -lightbluecloth:35:3 -yellowcloth:35:4 -lightgreencloth:35:5 -pinkcloth:35:6 -darkgraycloth:35:7 -darkgreycloth:35:7 -graycloth:35:7 -greycloth:35:7 -lightgraycloth:35:8 -lightgreycloth:35:8 -cyancloth:35:9 -tealcloth:35:9 -purplecloth:35:10 -bluecloth:35:11 -browncloth:35:12 -darkgreencloth:35:13 -redcloth:35:14 -blackcloth:35:15 -yellowflower:37 -yflower:37 -flower:37 -redrose:38 -rrose:38 -redflower:38 -rflower:38 -brownmushroom:39 -brownmush:39 -bmushroom:39 -bmush:39 -redmushroom:40 -redmush:40 -rmushroom:40 -rmush:40 -goldblock:41 -gblock:41 -ironblock:42 -iblock:42 -doublestep:43 -dstep:43 -doubleslab:43 -dslab:43 -step:44 -slab:44 -brickblock:45 -bblock:45 -tnt:46 -bookshelf:47 -bookblock:47 -mossycobblestone:48 -mossycobble:48 -mosscobble:48 -mcobble:48 -obsidian:49 -obsi:49 -obby:49 -torch:50 -fire:51 -mobspawner:52 -monsterspawner:52 -spawner:52 -woodenstairs:53 -woodstairs:53 -wstairs:53 -chest:54 -redstonewire:55 -redwire:55 -rswire:55 -diamondore:56 -dore:56 -diamondblock:57 -dblock:57 -workbench:58 -wbench:58 -workb:58 -crops:59 -crop:59 -soil:60 -furnace:61 -burningfurnace:62 -bfurnace:62 -signpost:63 -woodendoorhalf:64 -wooddoorhalf:64 -wdoorhalf:64 -ladder:65 -minecarttrack:66 -minecartrail:66 -track:66 -rail:66 -cobblestonestairs:67 -stonestairs:67 -cobblestairs:67 -sstairs:67 -cstairs:67 -wallsign:68 -lever:69 -stonepressureplate:70 -stoneplate:70 -splate:70 -irondoorhalf:71 -idoorhalf:71 -woodenpressureplate:72 -woodenplate:72 -woodplate:72 -wplate:72 -redstoneore:73 -rsore:73 -glowingredstoneore:74 -gredstoneore:74 -glowingrsore:74 -grsore:74 -redstonetorchoff:75 -redtorchoff:75 -rstorchoff:75 -redstonetorchon:76 -redstonetorch:76 -redtorchon:76 -redtorch:76 -rstorchon:76 -rstorch:76 -stonebutton:77 -sbutton:77 -button:77 -snowcovering:78 -snowcover:78 -ice:79 -snowblock:80 -sblock:80 -cactus:81 -clayblock:82 -cblock:82 -reedblock:83 -rblock:83 -jukebox:84 -jbox:84 -fence:85 -pumpkin:86 -netherack:87 -netherstone:87 -hellstone:87 -nstone:87 -hstone:87 -soulsand:88 -slowsand:88 -slowmud:88 -ssand:88 -smud:88 -mud:88 -glowstone:89 -lightstone:89 -lstone:89 -portal:90 -jackolantern:91 -pumpkinlantern:91 -glowingpumpkin:91 -lightpumpkin:91 -cakeblock:92 -#Item ID: -ironshovel:256 -ironspade:256 -ishovel:256 -ispade:256 -ironpickaxe:257 -ironpick:257 -ipickaxe:257 -ipick:257 -ironaxe:258 -iaxe:258 -flintandsteel:259 -lighter:259 -apple:260 -bow:261 -arrow:262 -coal:263 -diamond:264 -ironingot:265 -ironbar:265 -iingot:265 -ibar:265 -goldingot:266 -goldbar:266 -gingot:266 -gbar:266 -ironsword:267 -isword:267 -woodensword:268 -woodsword:268 -wsword:268 -woodenshovel:269 -woodenspade:269 -woodshovel:269 -woodspade:269 -wshovel:269 -wspade:269 -woodenpickaxe:270 -woodenpick:270 -woodpickaxe:270 -woodpick:270 -wpickaxe:270 -wpick:270 -woodenaxe:271 -woodaxe:271 -waxe:271 -stonesword:272 -ssword:272 -stoneshovel:273 -stonespade:273 -sshovel:273 -sspade:273 -stonepickaxe:274 -stonepick:274 -spickaxe:274 -spick:274 -stoneaxe:275 -saxe:275 -diamondsword:276 -dsword:276 -diamondshovel:277 -diamondspade:277 -dshovel:277 -dspade:277 -diamondpickaxe:278 -diamondpick:278 -dpickaxe:278 -dpick:278 -diamondaxe:279 -daxe:279 -stick:280 -bowl:281 -mushroomsoup:282 -mrsoup:282 -soup:282 -goldsword:283 -gsword:283 -goldshovel:284 -goldspade:284 -gshovel:284 -gspade:284 -goldpickaxe:285 -goldpick:285 -gpickaxe:285 -gpick:285 -goldaxe:286 -gaxe:286 -string:287 -rope:287 -feather:288 -gunpowder:289 -woodenhoe:290 -woodhoe:290 -whoe:290 -stonehoe:291 -shoe:291 -ironhoe:292 -ihoe:292 -diamondhoe:293 -dhoe:293 -goldhoe:294 -ghoe:294 -seeds:295 -seed:295 -wheat:296 -bread:297 -leatherhelmet:298 -leatherhelm:298 -leatherhat:298 -lhelmet:298 -lhelm:298 -lhat:298 -leatherchestplate:299 -leatherplatebody:299 -leathershirt:299 -lchestplate:299 -lplatebody:299 -lshirt:299 -leatherleggings:300 -leatherpants:300 -lleggings:300 -lpants:300 -leatherboots:301 -leathershoes:301 -lboots:301 -lshoes:301 -chainmailhelmet:302 -chainmailhelm:302 -chainmailhat:302 -cmhelmet:302 -cmhelm:302 -cmhat:302 -chainmailchestplate:303 -chainmailplatebody:303 -chainmailshirt:303 -cmchestplate:303 -cmplatebody:303 -cmshirt:303 -chainmailleggings:304 -chainmailpants:304 -cmleggings:304 -cmpants:304 -chainmailboots:305 -chainmailshoes:305 -cmboots:305 -cmshoes:305 -ironhelmet:306 -ironhelm:306 -ironhat:306 -ihelmet:306 -ihelm:306 -ihat:306 -ironchestplate:307 -ironplatebody:307 -ironshirt:307 -ichestplate:307 -iplatebody:307 -ishirt:307 -ironleggings:308 -ironpants:308 -ileggings:308 -ipants:308 -ironboots:309 -ironshoes:309 -iboots:309 -ishoes:309 -diamondhelmet:310 -diamondhelm:310 -diamondhat:310 -dhelmet:310 -dhelm:310 -dhat:310 -diamondchestplate:311 -diamondplatebody:311 -diamondshirt:311 -dchestplate:311 -dplatebody:311 -dshirt:311 -diamondleggings:312 -diamondpants:312 -dleggings:312 -dpants:312 -diamondboots:313 -diamondshoes:313 -dboots:313 -dshoes:313 -goldhelmet:314 -goldhelm:314 -goldhat:314 -ghelmet:314 -ghelm:314 -ghat:314 -goldchestplate:315 -goldplatebody:315 -goldshirt:315 -gchestplate:315 -gplatebody:315 -gshirt:315 -goldleggings:316 -goldpants:316 -gleggings:316 -gpants:316 -goldboots:317 -goldshoes:317 -gboots:317 -gshoes:317 -flint:318 -pork:319 -rawpork:319 -grilledpork:320 -cookedpork:320 -bacon:320 -painting:321 -picture:321 -goldenapple:322 -goldapple:322 -gapple:322 -sign:323 -woodendoor:324 -wooddoor:324 -wdoor:324 -bucket:325 -waterbucket:326 -wbucket:326 -lavabucket:327 -lbucket:327 -minecart:328 -cart:328 -saddle:329 -irondoor:330 -idoor:330 -redstone:331 -snowball:332 -boat:333 -leather:334 -milkbucket:335 -mbucket:335 -claybrick:336 -brick:336 -clayball:337 -clay:337 -reeds:338 -reed:338 -paper:339 -papyrus:339 -book:340 -slimeball:341 -sball:341 -storageminecart:342 -chestminecart:342 -storagecart:342 -chestcart:342 -sminecart:342 -cminecart:342 -scart:342 -ccart:342 -poweredminecart:343 -furnaceminecart:343 -poweredcart:343 -furnacecart:343 -pminecart:343 -fminecart:343 -pcart:343 -fcart:343 -egg:344 -compass:345 -fishingrod:346 -rod:346 -watch:347 -clock:347 -lightstonedust:348 -glowstonedust:348 -lsdust:348 -gsdust:348 -rawfish:349 -cookedfish:350 -dye:351 -inksack:351 -blackdye:351 -bdye:351 -reddye:351:1 -greendye:351:2 -browndye:351:3 -bluedye:351:4 -lapislazuli:351:4 -purpledye:351:5 -tealdye:351:6 -cyandye:351:6 -lightgraydye:351:7 -graydye:351:8 -pinkdye:351:9 -limedye:351:10 -lightgreendye:351:10 -yellowdye:351:11 -lightbluedye:351:12 -magentadye:351:13 -orangedye:351:14 -bonemeal:351:15 -whitedye:351:15 -bone:352 -sugar:353 -cake:354 -goldrecord:2256 -golddisk:2256 -grecord:2256 -gdisk:2256 -greenrecord:2257 -greendisk:2257 -grrecord:2257 -grdisk:2257 \ No newline at end of file diff --git a/src/itemsdb.yml b/src/itemsdb.yml new file mode 100644 index 0000000..9d1a4f1 --- /dev/null +++ b/src/itemsdb.yml @@ -0,0 +1,1120 @@ +######################## Items Definition File ######################### +# +# for changing item names, adding item aliases (and item sub-aliases), +# customising the displayed color, setting the item category, +# and defining sellable kits +# +######################################################################### +# items: + # item(id)[sub(data)]: +# (all fields optional) + # name: + # aliases: (1), (2), (...) + # sub: (1), (2), (...) (aliases for only if this item contains different items for different data values) + # color: (black, blue/dark blue, green/dark green, sky blue/dark sky blue"/aqua, + # red/dark red, magenta/purple, gold/amber/dark yellow, light gray/light grey, + # dark gray/dark grey/gray/grey, medium blue, light green/lime/lime green, + # cyan/light blue, orange/orange-red/red-orange, pink/light red/light purple, + # yellow, white +# categories are set in a seperate node (near the bottom of this file) +# colors are used when outputting the item name (overrides coloring in listing) +#if you have custom crafting recipies, you can add them here for dynamic pricing (when completed) +# craft syntax: itemID[:subData][@numUsed][+otheritems][=amount][,another recipe... ] +# example: item66: +# name: Rails +# craft: 265@6+280=16 +# ( or ironbar@6+stick=16 ) +#kits are similar, but without the + or = + +items: + item1: + name: Stone + aliases: smoothstone + item2: + name: Grass + aliases: + item3: + name: Dirt + aliases: + item4: + name: Cobblestone + aliases: cobble + item5: + name: Wood + aliases: plank + item6sub0: + name: Sapling + aliases: sapling, treesapling, tree, branch + item6sub1: + name: Spruce Sapling + sub: sprucetree + item6sub2: + name: Birch Sapling + aliases: pinecone + sub: birchtree, pine + item7: + name: Bedrock + aliases: adminium + item8: + name: Water + aliases: waterblock + item9: + name: Stationary Water + aliases: swater + item10: + name: Lava + aliases: lavablock + item11: + name: Stationary Lava + aliases: slava + item12: + name: Sand + aliases: + item13: + name: Gravel + aliases: + item14: + name: Gold Ore + aliases: gore + item15: + name: Iron Ore + aliases: iore + item16: + name: Coal Ore + aliases: core + item17: + name: Log + aliases: trunk + item17sub1: + name: Redwood + aliases: redlog + item17sub2: + name: Birch + aliases: whitelog + item18sub0: + name: Leaves + aliases: leaf, leafs + item18sub1: + name: Redwood Leaves + aliases: redleaves, rleaves, redleaf, rleaf + item18sub2: + name: Birch Leaves + aliases: bleaves, bleaf + item19: + name: Sponge + aliases: sponges + item20: + name: Glass + aliases: + item21: + name: Lapis Lazuli Ore + aliases: lapisore, lore + item22: + name: Lapis Lazuli Block + aliases: lapisblock, lblock + color: blue + item23: + name: Dispenser + aliases: + item24: + name: Sandstone + aliases: sstone + item25: + name: Note Block + aliases: musicblock, nblock, mblock + item26: + name: Bed Block + aliases: + item27: + name: Powered Rail + aliases: boosterrail, boosttrack, boostrack, poweredrail, poweredtrack + item28: + name: Detector Rail + aliases: detectorail + item29: + name: Sticky Piston + aliases: spiston, slimepiston + item30: + name: Web + aliases: spiderweb, cobweb + item31sub0: + name: Dead Fern + item31sub1: + name: Tall Grass + item31sub2: + name: Fern + item32: + name: Dead Shrub + item33: + name: Piston + item35sub0: + name: White Wool + aliases: cloth, whitecloth, whitewool, wool + sub: white, w + color: white + item35sub1: + name: Orange Wool + aliases: orangecloth + sub: orange, o + color: orange + item35sub2: + name: Magenta Wool + aliases: magentacloth + sub: magenta, m + color: magenta + item35sub3: + name: Light Blue Wool + aliases: lightbluecloth, lbluewool, lbluecloth + sub: lightblue, lblue, lb + color: light blue + item35sub4: + name: Yellow Wool + aliases: yellowcloth + sub: yellow, y + color: yellow + item35sub5: + name: Light Green Wool + aliases: lightgreencloth, limewool, limecloth, limegreenwool, limegreencloth + sub: lightgreen, lgreen, lg + color: lime + item35sub6: + name: Pink Wool + aliases: pinkcloth + sub: pink, pn + color: pink + item35sub7: + name: Dark Gray Wool + aliases: darkgreywool, graywool, greywool, darkgraycloth, darkgreycloth, graycloth, greycloth + sub: darkgray, dgray, darkgrey, dgrey, dg + color: gray + item35sub8: + name: Light Gray Wool + aliases: lightgreywool, lightgraycloth, lightgreycloth + sub: lightgray, lgray, lightgrey, lgrey lg + color: light gray + item35sub9: + name: Cyan Wool + aliases: tealwool, cyancloth, tealcloth + sub: cyan, teal, c, t + color: cyan + item35sub10: + name: Purple Wool + aliases: purplecloth + sub: purple, pu + color: magenta + item35sub11: + name: Blue Wool + aliases: bluecloth + sub: blue, blu + color: blue + item35sub12: + name: Brown Wool + aliases: browncloth + sub: brown, br + color: gray + item35sub13: + name: Dark Green Wool + aliases: darkgreencloth, greenwool, greencloth + sub: darkgreen, dgreen, dgr + color: green + item35sub14: + name: Red Wool + aliases: redcloth + sub: red, r + color: red + item35sub15: + name: Black Wool + aliases: blackcloth + sub: black + color: black + item37: + name: Yellow Flower + aliases: yflower, flower, flowers, dandelion + color: yellow + item38: + name: Red Rose + aliases: rrose, rose, redflower, rflower + color: red + item39: + name: Brown Mushroom + aliases: brownmush, bmushroom, bmush, mushroom + item40: + name: Red Mushroom + aliases: redmush, rmushroom, rmush + item41: + name: Gold Block + aliases: gblock + item42: + name: Iron Block + aliases: iblock + item43: + name: Double Step + aliases: dstep, doubleslab, dslab + item43sub1: + name: Sandstone Double Step + aliases: sanddoublestep, sdoublestep, sstonedoublestep, sanddoubleslab, sdslab, sdstep + item43sub2: + name: Wood Double Step + aliases: wdoublestep, wooddoubleslab, wdstep, wdslab + item43sub3: + name: Cobblestone Double Step + aliases: cstonedoublestep, cdoublestep, cobbledoublestoneslab, cobbledoubleslab, cdstep, cdslab + item44: + name: Step + aliases: slab, halfstep + item44sub1: + name: Sandstone Step + aliases: sandstep, sstep, sstonestep, sandslab + item44sub2: + name: Wood Step + aliases: wstep, woodslab + item44sub3: + name: Cobblestone Step + aliases: cstonestep, cstep, cobblestoneslab, cobbleslab + item45: + name: Brick Block + aliases: bblock + item46: + name: TNT + aliases: dynamite, dynomite + item47: + name: Bookshelf + aliases: bookblock + item48: + name: Moss Stone + aliases: mossycobblestone, mossycobble, mosscobble, mcobble, mosstone + item49: + name: Obsidian + aliases: obsi, obby + item50: + name: Torch + aliases: + item51: + name: Fire + aliases: + item52: + name: Monster Spawner + aliases: mobspawner, spawner, pigspawner + item53: + name: Wooden Stairs + aliases: woodstairs, wstairs + item54: + name: Chest + aliases: + item55: + name: Redstone Wire + aliases: redwire, rswire + item56: + name: Diamond Ore + aliases: dore + item57: + name: Diamond Block + aliases: dblock + item58: + name: Crafting Table + aliases: workbench, wbench, workb + item59: + name: Crops + aliases: crop + item60: + name: Soil + aliases: + item61: + name: Furnace + aliases: + item62: + name: Burning Furnace + aliases: bfurnace + item63: + name: Sign Post + aliases: + item64: + name: Wooden Door Half + aliases: wooddoorhalf, wdoorhalf + item65: + name: Ladder + aliases: + item66: + name: Rails + aliases: minecarttrack, minecartrail, track, rail + item67: + name: Cobblestone Stairs + aliases: stonestairs, cobblestairs, sstairs, cstairs + item68: + name: Wall Sign + aliases: + item69: + name: Lever + aliases: + item70: + name: Stone Pressure Plate + aliases: stoneplate, splate + item71: + name: Iron Door Half + aliases: idoorhalf + item72: + name: Wooden Pressure Plate + aliases: woodenplate, woodplate, wplate + item73: + name: Redstone Ore + aliases: rsore + item74: + name: Glowing Redstone Ore + aliases: gredstoneore, glowingrsore, grsore + item75: + name: Redstone Torch Off + aliases: redtorchoff, rstorchoff + item76: + name: Redstone Torch + aliases: redstonetorchon, redtorchon, redtorch, rstorchon, rstorch, rtorch + item77: + name: Stone Button + aliases: sbutton, button + item78: + name: Snow Covering + aliases: snowcover + item79: + name: Ice + aliases: + item80: + name: SnowBlock + aliases: sblock + item81: + name: Cactus + aliases: + item82: + name: Clay Block + aliases: cblock + item83: + name: Sugar Cane Block + aliases: reedblock, rblock, scblock + item84: + name: Jukebox + aliases: jbox + item85: + name: Fence + aliases: + item86: + name: Pumpkin + aliases: + item87: + name: Netherrack + aliases: netherack, netherstone, hellstone, nstone, hstone, bloodstone, redmossycobblestone + item88: + name: Soulsand + aliases: slowsand, slowmud, ssand, smud, mud + item89: + name: Glowstone + aliases: lightstone, lstone + item90: + name: Portal + aliases: + item91: + name: Jack O'Lantern + aliases: pumpkinlantern, glowingpumpkin, lightpumpkin + item92: + name: Cake Block + aliases: + item93: + name: Repeater Block Off + aliases: + item94: + name: Repeater Block On + aliases: + item95: + name: Locked Chest + aliases: + item96: + name: Trapdoor + aliases: tdoor + item97: + name: Silverfish Stone + aliases: monsterstone, silverstone + item98: + name: Stone Brick + aliases: +# items + item256: + name: Iron Shovel + aliases: ironspade, ishovel, ispade + item257: + name: Iron Pickaxe + aliases: ironpick, ipickaxe, ipick + item258: + name: Iron Axe + aliases: iaxe + item259: + name: Flint And Steel + aliases: lighter + item260: + name: Apple + aliases: + item261: + name: Bow + aliases: + item262: + name: Arrow + aliases: + item263sub0: + name: Coal + aliases: + item263sub1: + name: Charcoal + aliases: ccoal + item264: + name: Diamond + aliases: + item265: + name: Iron Ingot + aliases: ironignot, ironbar, iingot, ibar, iron + item266: + name: Gold Ingot + aliases: goldignot, goldbar, gingot, gbar, gold + item267: + name: Iron Sword + aliases: isword + item268: + name: Wooden Sword + aliases: woodsword, wsword + item269: + name: Wooden Shovel + aliases: woodenspade, woodshovel, woodspade, wshovel, wspade + item270: + name: Wooden Pickaxe + aliases: woodenpick, woodpickaxe, woodpick, wpickaxe, wpick + item271: + name: Wooden Axe + aliases: woodaxe, waxe + item272: + name: Stone Sword + aliases: ssword + item273: + name: Stone Shovel + aliases: stonespade, sshovel, sspade + item274: + name: Stone Pickaxe + aliases: stonepick, spickaxe, spick + item275: + name: Stone Axe + aliases: saxe + item276: + name: Diamond Sword + aliases: dsword + item277: + name: Diamond Shovel + aliases: diamondspade, dshovel, dspade + item278: + name: Diamond Pickaxe + aliases: diamondpick, dpickaxe, dpick + item279: + name: Diamond Axe + aliases: daxe + item280: + name: Stick + aliases: + item281: + name: Bowl + aliases: + item282: + name: Mushroom Soup + aliases: mrsoup, soup + item283: + name: Gold Sword + aliases: gsword + item284: + name: Gold Shovel + aliases: goldspade, gshovel, gspade + item285: + name: Gold Pickaxe + aliases: goldpick, gpickaxe, gpick + item286: + name: Gold Axe + aliases: gaxe + item287: + name: String + aliases: rope + item288: + name: Feather + aliases: feathers + item289: + name: Gunpowder + aliases: sulpher + item290: + name: Wooden Hoe + aliases: woodhoe, whoe + item291: + name: Stone Hoe + aliases: shoe + item292: + name: Iron Hoe + aliases: ihoe + item293: + name: Diamond Hoe + aliases: dhoe + item294: + name: Gold Hoe + aliases: ghoe + item295: + name: Seeds + aliases: seed + item296: + name: Wheat + aliases: + item297: + name: Bread + aliases: + item298: + name: Leather Helmet + aliases: leatherhelm, leatherhat, lhelmet, lhelm, lhat + item299: + name: Leather Chestplate + aliases: leatherplatebody, leathershirt, lchestplate, lplatebody, lshirt + item300: + name: Leather Leggings + aliases: leatherpants, lleggings, lpants + item301: + name: Leather Boots + aliases: leathershoes, lboots, lshoes + item302: + name: Chainmail Helmet + aliases: chainmailhelm, chainmailhat, cmhelmet, cmhelm, cmhat + item303: + name: Chainmail Chestplate + aliases: chainmailplatebody, chainmailshirt, cmchestplate, cmplatebody, cmshirt + item304: + name: Chainmail Leggings + aliases: chainmailpants, cmleggings, cmpants + item305: + name: Chainmail Boots + aliases: chainmailshoes, cmboots, cmshoes + item306: + name: Iron Helmet + aliases: ironhelm, ironhat, ihelmet, ihelm, ihat + item307: + name: Iron Chestplate + aliases: ironplatebody, ironshirt, ichestplate, iplatebody, ishirt + item308: + name: Iron Leggings + aliases: ironpants, ileggings, ipants + item309: + name: Iron Boots + aliases: ironshoes, iboots, ishoes + item310: + name: Diamond Helmet + aliases: diamondhelm, diamondhat, dhelmet, dhelm, dhat + item311: + name: Diamond Chestplate + aliases: diamondplatebody, diamondshirt, dchestplate, dplatebody, dshirt + item312: + name: Diamond Leggings + aliases: diamondpants, dleggings, dpants + item313: + name: Diamond Boots + aliases: diamondshoes, dboots, dshoes + item314: + name: Gold Helmet + aliases: goldhelm, goldhat, ghelmet, ghelm, ghat + item315: + name: Gold Chestplate + aliases: goldplatebody, goldshirt, gchestplate, gplatebody, gshirt + item316: + name: Gold Leggings + aliases: goldpants, gleggings, gpants + item317: + name: Gold Boots + aliases: goldshoes, gboots, gshoes + item318: + name: Flint + aliases: + item319: + name: Raw Porkchop + aliases: rawpork, pork, porkchop + item320: + name: Cooked Porkchop + aliases: grilledpork, cookedpork, bacon + item321: + name: Painting + aliases: picture + item322: + name: Golden Apple + aliases: goldapple, gapple + item323: + name: Sign + aliases: + item324: + name: Wooden Door + aliases: wooddoor, wdoor + item325: + name: Bucket + aliases: + item326: + name: Water Bucket + aliases: wbucket + item327: + name: Lava Bucket + aliases: lbucket + item328: + name: Minecart + aliases: cart + item329: + name: Saddle + aliases: + item330: + name: Iron Door + aliases: idoor + item331: + name: Redstone + aliases: redstonedust, reddust, rdust, rstone + item332: + name: Snowball + aliases: sb + item333: + name: Boat + aliases: + item334: + name: Leather + aliases: cowhide + item335: + name: Milk Bucket + aliases: mbucket + item336: + name: Clay Brick + aliases: brick + item337: + name: Clay Balls + aliases: clayball, clay + item338: + name: Sugar Cane + aliases: reed, scane + item339: + name: Paper + aliases: papyrus + item340: + name: Book + aliases: + item341: + name: Slimeball + aliases: sball, slimeballs + item342: + name: Storage Minecart + aliases: chestminecart, storagecart, chestcart, sminecart, cminecart, scart, ccart + item343: + name: Powered Minecart + aliases: furnaceminecart, poweredcart, furnacecart, pminecart, fminecart, pcart, fcart + item344: + name: Egg + aliases: + item345: + name: Compass + aliases: + item346: + name: Fishing Rod + aliases: rod + item347: + name: Clock + aliases: watch + item348: + name: Lightstone Dust + aliases: glowstonedust, lsdust, gsdust + item349: + name: Raw Fish + aliases: fish + item350: + name: Cooked Fish + aliases: cfish + item351sub0: + name: Ink Sac + aliases: blackdye, inksack, bdye, dye + sub: black + color: black + item351sub1: + name: Red Dye + aliases: + sub: red + color: red + item351sub2: + name: Green Dye + aliases: + sub: green + color: green + item351sub3: + name: Cocoa Bean + aliases: browndye, cocoa, coco, cocobean + sub: brown + color: gray + item351sub4: + name: Lapis Lazuli + aliases: bluedye, lapislazul, lapis + sub: blue + color: blue + item351sub5: + name: Purple Dye + aliases: + sub: purple + color: magenta + item351sub6: + name: Cyan Dye + aliases: tealdye + sub: cyan, teal + color: cyan, teal + item351sub7: + name: Light Gray Dye + aliases: lightgreydye + sub: lightgrey, lightgray + color: light gray + item351sub8: + name: Gray Dye + aliases: greydye + sub: grey, gray + color: gray + item351sub9: + name: Pink Dye + aliases: + sub: pink + color: pink + item351sub10: + name: Lime Dye + aliases: lightgreendye + sub: lime, limegreen + color: lime + item351sub11: + name: Yellow Dye + aliases: + sub: yellow + color: yellow + item351sub12: + name: Light Blue Dye + aliases: + sub: lightblue + color: sky blue + item351sub13: + name: Magenta Dye + aliases: + sub: magenta + color: magenta + item351sub14: + name: Orange Dye + aliases: + sub: orange + color: orange + item351sub15: + name: Bone Meal + aliases: whitedye + sub: white + color: white + item352: + name: Bone + aliases: + item353: + name: Sugar + aliases: + item354: + name: Cake + aliases: + item355: + name: Bed + aliases: cot, beds + item356: + name: Redstone Repeater + aliases: repeater, redstoneextender, extender + item357: + name: Cookie + aliases: + item358: + name: Map + item359: + name: Shears + item360: + name: Melon Slice + item361: + name: Pumpkin Seeds + aliases: + item362: + name: Melon Seeds + aliases: + item363: + name: Raw Beef + aliases: beef, cowmeat + item364: + name: Steak + aliases: cookedbeef, cookedcow + item365: + name: Raw Chicken + aliases: chicken + item366: + name: Cooked Chicken + aliases: + item367: + name: Rotten Flesh + aliases: flesh + item368: + name: Ender Pearl + aliases: epearl + + item369: + name: Blaze Rod + aliases: + item370: + name: Ghast Tear + aliases: + item371: + name: Gold Nugget + aliases: + item372: + name: Nether Wart + aliases: + item374: + name: Glass Bottle + aliases: + item375: + name: Spider Eye + aliases: + item376: + name: Fermented Spider Eye + aliases: + item377: + name: Blaze Powder + aliases: + item378: + name: Magma Cream + aliases: + item379: + name: Brewing Stand + aliases: + item380: + name: Cauldron + aliases: + item381: + name: Eye of Ender + aliases: + item382: + name: Glistering Melon + aliases: + item383sub50: + name: Creeper Egg + aliases: + item383sub51: + name: Skeleton Egg + aliases: + item383sub52: + name: Spider Egg + aliases: + item383sub53: + name: Giant Egg + aliases: + item383sub54: + name: Zombie Egg + aliases: + item383sub55: + name: Slime Egg + aliases: + item383sub56: + name: Ghast Egg + aliases: + item383sub57: + name: PigZombie Egg + aliases: + item383sub58: + name: Enderman Egg + aliases: + item383sub59: + name: CaveSpider Egg + aliases: + item383sub60: + name: Silverfish Egg + aliases: + item383sub61: + name: Blaze Egg + aliases: + item383sub62: + name: LavaSlime Egg + aliases: + item383sub63: + name: EnderDragon Egg + aliases: + item383sub90: + name: Pig Egg + aliases: + item383sub91: + name: Sheep Egg + aliases: + item383sub92: + name: Cow Egg + aliases: + item383sub93: + name: Chicken Egg + aliases: + item383sub94: + name: Wolf Egg + aliases: + item383sub95: + name: MushroomCow Egg + aliases: + item383sub96: + name: SnowMan Egg + aliases: + item383sub97: + name: Villager Egg + aliases: + item383sub98: + name: Ocelot Egg + aliases: categg + item384: + name: Bottle o' Enchanting + aliases: BottleofEnchanting, experiencebottle, experience + item385: + name: Fire Charge + aliases: fireball + item2256: + name: 13 Disk + aliases: goldrecord, golddisk, grecord, gdisk + color: gold + item2257: + name: Cat Disc + aliases: greenrecord, greendisk, grrecord, grdisk + color: green + item2258: + name: Blocks Disc + aliases: orangerecord, orangedisk, orrecord, ordisk + color: orange + item2259: + name: Chirp Disc + aliases: redrecord, reddisk, rrecord, rdisk + color: red + item2260: + name: Far Disc + aliases: limerecord, limedisk, lrecord, ldisk + color: + item2261: + name: Mall Disc + aliases: bluerecord, bluedisk + color: blue + item2262: + name: Mellohi Disc + aliases: purplerecord, purpledisk, precord, pdisk + color: purple + item2263: + name: Stal Disc + aliases: blackrecord, blackdisk + color: black + item2264: + name: Strad Disc + aliases: whiterecord, whitedisk, wrecord, wdisk + color: white + item: + name: + aliases: record, disk, record, disk + color: + item2265: + name: Ward Disc + color: lime + item: + name: 11 Disc + aliases: brokenrecord, brokendisk + color: black + +#entities :) +# can't add more than what i have, or you'll get a chicken +# note: if adding to the csv, add 4000 to the entity number +entities: + entity0: + name: Chicken + aliases: + entity1: + name: Cow + aliases: + entity2: + name: Creeper + aliases: + entity3: + name: Ghast + aliases: + entity4: + name: Giant + aliases: giantzombie + entity5: + name: Monster + aliases: human, slave, bodyguard, dummy + entity6: + name: Pig + aliases: + entity7: + name: PigZombie + aliases: + entity8: + name: Sheep + aliases: + entity9: + name: Skeleton + aliases: + entity10: + name: Slime + aliases: + entity11: + name: Spider + aliases: + entity12: + name: Squid + aliases: + entity13: + name: Zombie + aliases: + entity14: + name: Wolf + aliases: dog + entity15: + name: Cave Spider + aliases: cspider + entity16: + name: Enderman + aliases: endermen + entity17: + name: Silverfish + aliases: sliverfish, stonefish + entity18: + name: Ender Dragon + aliases: + entity19: + name: Villager + aliases: + entity20: + name: Blaze + aliases: + entity21: + name: Mushroom Cow + aliases: mooshroom + + + +# accepted are item names (and id:data), names (but names must be updated if changed), and aliases +categories: + food: apple, goldenapple, bread, pork, cookedpork, rawfish, cookedfish, cake, cookie + loot: string, arrow, gunpowder, feather + armor: 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317 + tools: 256, 257, 258, 269, 270, 271, 273, 274, 275, 277, 278, 279, 284, 285, 286, 290, 291, 292, 293, 294, 346, 359 + weapons: 261, 262, 267, 268, 272, 276, 283 + #wool: "35, 35:12, 35:10, 35:2, 35:11, 35:3, 35:9, 35:13, 35:5, 35:4, 35:1, 35:14, 35:6, 35:8, 35:7, 35:15" + dye: "351:15, 351:3, 351:5, 351:13, 351:4, 351:12, 351:6, 351:2, 351:11, 351:14, 351:1, 351:9, 351:7, 351:8, 351" + #dye: "351, 351:1, 351:2, 351:3, 351:4, 351:5, 351:6, 351:7, 351:8, 351:9, 351:10, 351:11, 351:12, 351:13, 351:14, 351:15" + entities: 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021 + animals: 4000, 4001, 4006, 4008, 4014, 4021 + kits: 5000, 5001, 5002 + +#kits can be added here (must be numbered, starting at 1, starts at 5000 in pricelist database) +# kits can be bought, not sold +#numbering must be unique if the item has been added to a pricelist.. +# changing the kit number would also render the pricelist entry invalid or to the wrong kit +kits: + kit1: + name: Diamond Armor + aliases: darmor + items: 310, 311, 312, 313 + color: light blue + kit2: + name: Paint Set + aliases: dyes, painter + items: 351@5, 351:1@5, 351:2@5, 351:3@5, 351:4@5, 351:5@5, 351:6@5, 351:7@5, 351:8@5, 351:9@5, 351:10@5, 351:11@5, 351:12@5, 351:13@5, 351:14@5, 351:15@5 + kit3: + name: Rare Items + aliases: + items: diamond@10, lapis@20 diff --git a/src/me/jascotty2/bettershop/BSConfig.java b/src/me/jascotty2/bettershop/BSConfig.java new file mode 100644 index 0000000..071925f --- /dev/null +++ b/src/me/jascotty2/bettershop/BSConfig.java @@ -0,0 +1,941 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: BetterShop plugin configuration settings + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.MinecraftChatStr; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Level; +import me.jascotty2.bettershop.enums.CommandShopMode; +import me.jascotty2.bettershop.enums.DBType; +import me.jascotty2.bettershop.enums.DiscountMethod; +import me.jascotty2.bettershop.enums.EconMethod; +import me.jascotty2.bettershop.enums.SpoutCategoryMethod; +import me.jascotty2.bettershop.shop.ShopConfig; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import org.bukkit.ChatColor; + +import me.jascotty2.lib.bukkit.config.Configuration; +import me.jascotty2.lib.bukkit.config.ConfigurationNode; + +public class BSConfig { + +// + // chat messages + private final HashMap stringMap = new HashMap(); + // files used by plugin + public final static String configname = "config.yml"; + public final static File pluginFolder = new File("plugins", "BetterShop"); + public final static File configfile = new File(pluginFolder, configname); + public final static File itemDBFile = new File(pluginFolder, "itemsdb.yml"); + public final static File signDBFile = new File(pluginFolder, "signs.dat"); + public final static File chestDBFile = new File(pluginFolder, "chests.dat"); + ///// plugin settings + public boolean checkUpdates = true, + sendErrorReports = true, + unMaskErrorID = false, + autoUpdate = false; + public boolean sendLogOnError = true, sendAllLog = false; + public boolean hideHelp = false; + public static final int MAX_CUSTMSG_LEN = 90; + ////// shop settings + public String defaultCurrency = "Coin", pluralCurrency = "Coins"; + public int pagesize = 9; + public boolean publicmarket = false; + public String defColor = "white", + BOSBank = ""; + protected String customErrorMessage = ""; + public final ShopConfig mainShopConfig = new ShopConfig(); + public EconMethod econ = EconMethod.AUTO; + ///// item buying behavior + public boolean allowbuyillegal = true, //if someone without BetterShop.admin.illegal can buy illegal items + usemaxstack = true, //whether maxstack should be honored + buybacktools = true, //used tools can be bought back? + buybackenabled = true; //shop buys items from users? + public int maxEntityPurchase = 3; // max can purchase at a time + // sign settings + public boolean signShopEnabled = true, + signDestroyProtection = true, + signWEprotection = false, + signTNTprotection = true, + tntSignDestroyProtection = false, + signItemColor = false, //color the names of items on signs? + signItemColorBWswap = false;// swap black & white item colors?;; + public String activeSignColor = "blue"; // automatically changed to \u00A7 format + public static long signInteractWait = 1000; // wait before another action allowed + // chest shop + public String chestShopText = "BetterShop Chest Shop"; + public boolean chestShopEnabled = true, + chestDestroyProtection = true, + chestTNTprotection = true, + chestSellBar = false; + public String chestText = "BetterShop Chest Shop ", + chestEditText = "(Editing)"; + // global or region shops + public CommandShopMode commandShopMode = CommandShopMode.GLOBAL; + ////// database information + private DBType databaseType = DBType.FLATFILE; + /// database caching (MySQL only) + public boolean useDBCache = true; + //public BigInteger priceListLifespan = new BigInteger("0"); // 0 = never update + public long priceListLifespan = 0; // 0 = never update + public int tempCacheTTL = 10; //how long before tempCache is considered outdated (seconds) + ////// logging settings + public boolean logUserTransactions = false, + logTotalTransactions = false, + logCommands = false; + //public BigInteger userTansactionLifespan = new BigInteger("172800"); // 2 days.. i'd like to use unsigned long, but java doesn't have it.. + public long userTansactionLifespan = 172800; + public String commandFilename = "Commands.log"; + // if pricelist is to have a custom sort order, what should be at the top + public ArrayList sortOrder = new ArrayList(); + // dynamic pricing options + public boolean useDynamicPricing = false; + //if a price is not set on a craftable item, can still sell if the materials to make are for sale + public boolean sellcraftables = true; + // if sellcraftables, how much % more a crafted item costs than the materials + public double sellcraftableMarkup = .05; + // if sellcraftables, if colored wool should sell for less than sellcraftableMarkup + // (otherwise, a player could buy dye, color a sheep, get 1-3 colored wool, make profit) + public boolean woolsellweight = true; + // stock items + public boolean useItemStock = false; + // spout-related + public boolean spoutEnabled = true; + private String spoutKey = "B"; + public boolean largeSpoutMenu = true, + spoutUsePages = false, + spoutCatCustomSort = true, + spoutUseScroll = false; + public SpoutCategoryMethod spoutCategories = SpoutCategoryMethod.NONE; + // discount permissions groups + HashMap groups = new HashMap(); + public DiscountMethod discountSellingMethod = DiscountMethod.LOWER; +// + + public BSConfig() { + } + + public final boolean load() { + // in case load errors out, ensure strings are present + + stringMap.clear(); + // default strings + // # general messages + stringMap.put("prefix", "&fSHOP: &2"); + stringMap.put("permdeny", "OI! You don't have permission to do that!"); + stringMap.put("unkitem", "What is &f&2?"); + stringMap.put("nicetry", "...Nice try!"); + stringMap.put("logformat", "/ :: > "); + // # shopadd messages + stringMap.put("paramerror", "Oops... something wasn't right there."); + stringMap.put("addmsg", "&f[&f]&2 added to the shop. Buy: &f&2 Sell: &f"); + stringMap.put("chgmsg", "&f[&f]&2 updated. Buy: &f&2 Sell: &f"); + // # shopremove messages + stringMap.put("removemsg", "&f[&f]&2 removed from the shop"); + // # shopcheck messages + stringMap.put("pricecheck", "Price check! &f[&f]&2 Buy: &f &2Sell: &f" + (useItemStock ? " (stock: )" : "")); + stringMap.put("multipricecheck", "Price check! &f[&f]&2 Buy: &f &2Sell: &f"); + stringMap.put("multipricechecksell", "Price check! &f[&f]&2 Sell: &f"); + stringMap.put("multipricecheckbuy", "Price check! &f[&f]&2 Buy: &f"); + stringMap.put("nolisting", "&f[&f] &2cannot be bought or sold."); + // # shoplist messages + stringMap.put("listhead", "-------- Price-List Page: &f &2of &f &2--------"); + stringMap.put("listing", "&f[&f]&2 Buy: &f&2 Sell: &f" + (useItemStock ? " (stock: )" : "")); + stringMap.put("listtail", "-----------------------------------------"); + stringMap.put("listalias", "&f[&f]&2 is also known as: &b"); + // # shopbuy messages + stringMap.put("buymsg", "Buying &f &2&2 at &f &2 each... &f &2 total!"); + stringMap.put("publicbuymsg", " bought &f &2&2 for &f"); + stringMap.put("outofroom", "You tried to buy &f&2 too many.. you can only hold &2 more"); + stringMap.put("insuffunds", "&4You don't have enough ! &f &2 at &f &2 each = &c"); + stringMap.put("notforsale", "&f[&f] &2cannot be bought"); + stringMap.put("illegalbuy", "&4you don't have permissions to buy &f[&f]"); + // # shopsell messages + stringMap.put("donthave", "You only have , not "); + stringMap.put("donotwant", "&f[&f] &2has no value to me. No thanks."); + stringMap.put("sellmsg", "Selling &f &2&2 at &f &2 each... &f &2 total!"); + stringMap.put("publicsellmsg", " sold &f &2&2 for &f"); + // # stock messages + stringMap.put("outofstock", "This item is currently out of stock"); + stringMap.put("lowstock", "Only avaliable for purchase"); + stringMap.put("maxstock", "This item is currently at max stock"); + stringMap.put("highstock", "Only can be sold back"); + + stringMap.put("regionShopDisabled", "You cannot access the store from here!"); + + try { + Configuration config = new Configuration(configfile); + config.load(); + ConfigurationNode n; + + // check for completedness + try { + HashMap allKeys = new HashMap(); + allKeys.put("shop", new String[]{ + "ItemsPerPage", "publicmarket", + "logcommands", "commandLogFile", "logformat", + "allowbuyillegal", "usemaxstack", + "buybacktools", "buybackenabled", "maxEntityPurchase", + "signShops", + "activeSignColor", "signItemColor", + "signItemColorBWswap", + "signDestroyProtection", + "weSignDestroyProtection", + "tntSignDestroyProtection", + "chestShops", + "chestDestroyProtection", + "tntChestDestroyProtection", + "chestText", "chestEditText", "chestSellBar", + "sellDiscountMethod", + "commandShop", + "customsort", + "defaultItemColor", + "tablename", + "hideHelp", + "BOSBank", + "currencyName", + "economy"}); + allKeys.put("errors", new String[]{ + "CheckForUpdates", + "AutoUpdate", + "AutoErrorReporting", + "UnMaskErrorID", + "CustomErrorMessage", + "sendLogOnError", + "sendAllLog"}); + allKeys.put("spout", new String[]{ + "enabled", + "key", + "largeMenu", + "usePages", + "categories", + "useSort"}); + allKeys.put("MySQL", new String[]{ + "useMySQL", + "database", + "username", + "password", + "Hostname", + "Port", + "cache", + "cacheUpdate", + "tempCacheTTL"}); + allKeys.put("transactionLog", new String[]{ + "enabled", + "logtablename", + "userTansactionLifespan"}); + allKeys.put("totalsTransactionLog", new String[]{ + "enabled", + "logtablename"}); + allKeys.put("dynamicMarket", new String[]{ + "dynamicPricing", + "sellcraftables", + "sellcraftableMarkup", + "woolsellweight"}); + allKeys.put("itemStock", new String[]{ + "enabled", + "tablename", + "startStock", + "maxStock", + "noOverStock", + "restock"}); + allKeys.put("strings", stringMap.keySet().toArray(new String[0])); + String allowNull[] = new String[]{ + "shop.customsort", "shop.BOSBank", "shop.currencyName", "shop.sellDiscountMethod", + "strings.listtail", "strings.logformat"}; + + String missing = "", unused = ""; + for (String k : allKeys.keySet()) { + if (k == null) { + //k = ""; + continue; + } else if (config.getKeys(k) == null) { + if (missing.length() > 0) { + missing += ", " + k + ".*"; + } else { + missing += k + ".*"; + } + continue; + } + String key = ""; + if (k.length() > 0) { + key = k + "."; + for (String val : config.getKeys(k)) { + if (indexOf(allKeys.get(k), val) < 0) { + if (unused.length() > 0) { + unused += ", " + key + val; + } else { + unused += key + val; + } + + } + } + } + for (String val : allKeys.get(k)) { + if (config.getProperty(key + val) == null && indexOf(allowNull, key + val) < 0) { + //missing.add(key+val); + if (missing.length() > 0) { + missing += ", " + key + val; + } else { + missing += key + val; + } + } + } + } + if (unused.length() > 0) { + BetterShopLogger.Log("Notice: Unused Configuration Nodes: \n" + unused); + } + if (missing.length() > 0) { + BetterShopLogger.Log("Missing Configuration Nodes: \n" + missing); + } + } catch (Exception ex) { + // this shouldn't be happening: send error report + BetterShopLogger.Log(Level.SEVERE, "Unexpected Error during config integrety check", ex); + } + + // supporting these older nodes for now + boolean usingOldMySQLsetting = false; + if (configHasNode(config, new String[]{"CheckForUpdates", "AutoUpdate", + "AutoErrorReporting", "UnMaskErrorID", "CustomErrorMessage", + "ItemsPerPage", "publicmarket", "allowbuyillegal", "usemaxstack", + "buybacktools", "buybackenabled", "maxEntityPurchase", + "tablename", "useMySQL", "useMySQLPricelist", "defaultItemColor", + "sendLogOnError", "sendAllLog", "hideHelp", "customsort"})) { + BetterShopLogger.Log(Level.WARNING, "Using Deprecated Configuration Nodes: Update To New Format Soon!"); + + checkUpdates = config.getBoolean("CheckForUpdates", checkUpdates); + autoUpdate = config.getBoolean("AutoUpdate", autoUpdate); + sendErrorReports = config.getBoolean("AutoErrorReporting", sendErrorReports); + unMaskErrorID = config.getBoolean("UnMaskErrorID", unMaskErrorID); + customErrorMessage = config.getString("CustomErrorMessage", customErrorMessage).trim(); + if (customErrorMessage.length() > MAX_CUSTMSG_LEN) { + BetterShopLogger.Log("Notice: CustomErrorMessage is too long. (will be truncated)"); + customErrorMessage = customErrorMessage.substring(0, MAX_CUSTMSG_LEN); + } + + pagesize = config.getInt("ItemsPerPage", pagesize); + publicmarket = config.getBoolean("publicmarket", publicmarket); + + allowbuyillegal = config.getBoolean("allowbuyillegal", allowbuyillegal); + usemaxstack = config.getBoolean("usemaxstack", usemaxstack); + buybacktools = config.getBoolean("buybacktools", buybacktools); + buybackenabled = config.getBoolean("buybackenabled", buybackenabled); + maxEntityPurchase = config.getInt("maxEntityPurchase", maxEntityPurchase); + + mainShopConfig.tableName = config.getString("tablename", mainShopConfig.tableName); + + if (config.getProperty("useMySQL") != null || config.getProperty("useMySQLPricelist") != null) { + usingOldMySQLsetting = true; + databaseType = config.getBoolean("useMySQL", config.getBoolean("useMySQLPricelist", false)) ? DBType.MYSQL : DBType.FLATFILE; + if (databaseType == DBType.MYSQL) { + n = config.getNode("MySQL"); + if (n != null) { + mainShopConfig.sql_username = n.getString("username", mainShopConfig.sql_username); + mainShopConfig.sql_password = n.getString("password", mainShopConfig.sql_password); + mainShopConfig.sql_database = n.getString("database", mainShopConfig.sql_database).replace(" ", "_"); + mainShopConfig.sql_hostName = n.getString("Hostname", mainShopConfig.sql_hostName); + mainShopConfig.sql_portNum = n.getString("Port", mainShopConfig.sql_portNum); + String lifespan = n.getString("tempCacheTTL"); + if (lifespan != null) { + tempCacheTTL = CheckInput.GetInt(lifespan, tempCacheTTL); + } + useDBCache = n.getBoolean("cache", useDBCache); + lifespan = n.getString("cacheUpdate"); + if (lifespan != null) { + try { + priceListLifespan = CheckInput.GetBigInt_TimeSpanInSec(lifespan, 'h').longValue(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.WARNING, "cacheUpdate has an illegal value"); + BetterShopLogger.Log(Level.WARNING, ex); + } + } + } else { + BetterShopLogger.Log(Level.WARNING, "MySQL section in " + configname + " is missing"); + } + } + } + + defColor = config.getString("defaultItemColor", defColor); + JItemDB.setDefaultColor(defColor); + + sendLogOnError = config.getBoolean("sendLogOnError", sendLogOnError); + sendAllLog = config.getBoolean("sendAllLog", sendAllLog); + + hideHelp = config.getBoolean("hideHelp", hideHelp); + BOSBank = config.getString("BOSBank", ""); + + String customsort = config.getString("customsort"); + if (customsort != null) { + // parse for items && add to custom sort arraylist + String items[] = customsort.split(","); + for (String i : items) { + JItem toAdd = JItemDB.findItem(i.trim()); + if (toAdd != null) { + sortOrder.add(toAdd.IdDatStr()); + } else { + BetterShopLogger.Log("Invalid Item in customsort configuration: " + i); + } + } + } + } // end depricated settings block + + if ((n = config.getNode("errors")) != null) { + checkUpdates = n.getBoolean("CheckForUpdates", checkUpdates); + autoUpdate = n.getBoolean("AutoUpdate", autoUpdate); + sendErrorReports = n.getBoolean("AutoErrorReporting", sendErrorReports); + unMaskErrorID = n.getBoolean("UnMaskErrorID", unMaskErrorID); + customErrorMessage = n.getString("CustomErrorMessage", customErrorMessage).trim(); + if (customErrorMessage.length() > MAX_CUSTMSG_LEN) { + BetterShopLogger.Log("Notice: CustomErrorMessage is too long. (will be truncated)"); + customErrorMessage = customErrorMessage.substring(0, MAX_CUSTMSG_LEN); + } + sendLogOnError = n.getBoolean("sendLogOnError", sendLogOnError); + sendAllLog = n.getBoolean("sendAllLog", sendAllLog); + } + + if ((n = config.getNode("shop")) != null) { + pagesize = n.getInt("ItemsPerPage", pagesize); + publicmarket = n.getBoolean("publicmarket", publicmarket); + + logCommands = n.getBoolean("logcommands", logCommands); + if (n.getString("commandLogFile") != null) { + commandFilename = n.getString("commandLogFile"); + } + stringMap.put("logformat", n.getString("logformat", stringMap.get("logformat"))); + + allowbuyillegal = n.getBoolean("allowbuyillegal", allowbuyillegal); + usemaxstack = n.getBoolean("usemaxstack", usemaxstack); + buybacktools = n.getBoolean("buybacktools", buybacktools); + buybackenabled = n.getBoolean("buybackenabled", buybackenabled); + maxEntityPurchase = n.getInt("maxEntityPurchase", maxEntityPurchase); + + signShopEnabled = n.getBoolean("signShops", signShopEnabled); + activeSignColor = n.getString("activeSignColor", activeSignColor); + signDestroyProtection = n.getBoolean("signDestroyProtection", signDestroyProtection); + signWEprotection = n.getBoolean("weSignDestroyProtection", signWEprotection); + signTNTprotection = n.getBoolean("tntSignDestroyProtection", signTNTprotection); + + chestShopEnabled = n.getBoolean("chestShops", chestShopEnabled); + chestSellBar = n.getBoolean("chestSellBar", chestSellBar); + chestDestroyProtection = n.getBoolean("chestDestroyProtection", chestDestroyProtection); + chestTNTprotection = n.getBoolean("tntChestDestroyProtection", chestTNTprotection); + + chestText = n.getString("chestText", chestText); + chestEditText = n.getString("chestEditText", chestEditText); + + signItemColor = n.getBoolean("signItemColor", signItemColor); + signItemColorBWswap = n.getBoolean("signItemColorBWswap", signItemColorBWswap); + + String sd = n.getString("sellDiscountMethod"); + if(sd != null) { + if(sd.equalsIgnoreCase("None")) { + discountSellingMethod = DiscountMethod.NONE; + } else if(sd.equalsIgnoreCase("Higher")) { + discountSellingMethod = DiscountMethod.HIGHER; + } else { + if(!sd.equalsIgnoreCase("Lower")) { + BetterShopLogger.Warning("Invalid setting in shop.sellDiscountMethod" + sd); + } + discountSellingMethod = DiscountMethod.LOWER; + } + } + + String cShopMode = n.getString("commandShop"); + if (cShopMode != null) { + if (cShopMode.equalsIgnoreCase("disabled") + || cShopMode.equalsIgnoreCase("none")) { + commandShopMode = CommandShopMode.NONE; + } else if (cShopMode.equalsIgnoreCase("regions") + || cShopMode.equalsIgnoreCase("region")) { + commandShopMode = CommandShopMode.REGIONS; + } else if (cShopMode.equalsIgnoreCase("both")) { + commandShopMode = CommandShopMode.BOTH; + } else { + if (!cShopMode.equalsIgnoreCase("global")) { + BetterShopLogger.Warning("Invalid setting in shop.commandShop: " + cShopMode); + } + commandShopMode = CommandShopMode.GLOBAL; + } + } + + mainShopConfig.tableName = n.getString("tablename", mainShopConfig.tableName); + + defColor = n.getString("defaultItemColor", defColor); + JItemDB.setDefaultColor(defColor); + + //ItemCurrency.loadFromString(n.getString("currencyItems", "diamond>20, goldbar>5, ironbar>1, redstone>.5")); + defaultCurrency = n.getString("currencyName", "Coin"); + pluralCurrency = n.getString("currencyNamePlural", "Coins"); + + String customsort = n.getString("customsort"); + if (customsort != null) { + // parse for items && add to custom sort arraylist + String items[] = customsort.split(","); + for (String i : items) { + JItem toAdd = JItemDB.findItem(i.trim()); + if (toAdd != null) { + sortOrder.add(toAdd.IdDatStr()); + } else { + BetterShopLogger.Log("Invalid Item in customsort configuration: " + i); + } + } + } + hideHelp = n.getBoolean("hideHelp", hideHelp); + + String t = n.getString("economy"); + if(t != null) { + if(t.equalsIgnoreCase("exp")) { + econ = EconMethod.EXP; + } else if(t.equalsIgnoreCase("total")) { + econ = EconMethod.TOTAL; + } +// else if(t.equalsIgnoreCase("bultin")) { +// econ = EconMethod.BULTIN; +// } + else { + econ = EconMethod.AUTO; + if(!t.equalsIgnoreCase("auto")) { + BetterShopLogger.Log("shop.economy has an invalid value: " + t + " (defaulting to auto)"); + } + } + } + } + if (activeSignColor.equalsIgnoreCase("blue")) { + // "blue" looks like light purple on a sign + activeSignColor = "dark blue"; + } + activeSignColor = MinecraftChatStr.getChatColorStr(activeSignColor, ChatColor.BLUE); + + if ((n = config.getNode("spout")) != null) { + spoutEnabled = n.getBoolean("enabled", spoutEnabled); + largeSpoutMenu = n.getBoolean("largeMenu", largeSpoutMenu); + spoutUsePages = n.getBoolean("usePages", spoutUsePages); + setSpoutKey(n.getString("key", spoutKey)); + String c = n.getString("categories"); + if (c != null) { + if (c.equalsIgnoreCase("cycle")) { + spoutCategories = SpoutCategoryMethod.CYCLE; + } else if (c.equalsIgnoreCase("tab") || c.equalsIgnoreCase("tabbed")) { + spoutCategories = SpoutCategoryMethod.TABBED; + } else { + spoutCategories = SpoutCategoryMethod.NONE; + } + } + spoutCatCustomSort = n.getBoolean("useSort", spoutCatCustomSort); + } + + // groups + if ((n = config.getNode("discountGroups")) != null) { + groups.clear(); + for (String g : n.getKeys()) { + if (!CheckInput.IsDouble(n.getString(g))) { + BetterShopLogger.Log(Level.WARNING, "Invalid discount set for " + g); + } else { + double d = CheckInput.GetDouble(n.getString(g), 0) / 100; + if (d > 1) { + d = 1; + } + groups.put(g, d); + } + } + } + + if (!usingOldMySQLsetting && (n = config.getNode("MySQL")) != null) { + databaseType = n.getBoolean("useMySQL", config.getBoolean("useMySQLPricelist", false)) ? DBType.MYSQL : DBType.FLATFILE; + if (databaseType == DBType.MYSQL) { + mainShopConfig.sql_username = n.getString("username", mainShopConfig.sql_username); + mainShopConfig.sql_password = n.getString("password", mainShopConfig.sql_password); + mainShopConfig.sql_database = n.getString("database", mainShopConfig.sql_database).replace(" ", "_"); + mainShopConfig.sql_hostName = n.getString("Hostname", mainShopConfig.sql_hostName); + mainShopConfig.sql_portNum = n.getString("Port", mainShopConfig.sql_portNum); + String lifespan = n.getString("tempCacheTTL"); + if (lifespan != null) { + tempCacheTTL = CheckInput.GetInt(lifespan, tempCacheTTL); + } + useDBCache = n.getBoolean("cache", useDBCache); + lifespan = n.getString("cacheUpdate"); + if (lifespan != null) { + try { + priceListLifespan = CheckInput.GetBigInt_TimeSpanInSec(lifespan, 'h').longValue(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.WARNING, "cacheUpdate has an illegal value"); + BetterShopLogger.Log(Level.WARNING, ex); + } + } + } + } + + if ((n = config.getNode("transactionLog")) != null) { + logUserTransactions = n.getBoolean("enabled", logUserTransactions); + mainShopConfig.transLogTablename = n.getString("logtablename", mainShopConfig.transLogTablename).replace(" ", "_"); + String lifespan = n.getString("userTansactionLifespan"); + if (lifespan != null) { + try { + userTansactionLifespan = CheckInput.GetBigInt_TimeSpanInSec(lifespan).longValue(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.WARNING, "userTansactionLifespan has an illegal value", ex); + } + } + } + + if ((n = config.getNode("totalsTransactionLog")) != null) { + logTotalTransactions = n.getBoolean("enabled", logTotalTransactions); + mainShopConfig.recordTablename = n.getString("logtablename", mainShopConfig.recordTablename).replace(" ", "_"); + } + + if ((n = config.getNode("dynamicMarket")) != null) { + useDynamicPricing = n.getBoolean("dynamicPricing", n.getBoolean("enabled", useDynamicPricing)); + sellcraftables = n.getBoolean("sellcraftables", sellcraftables); + sellcraftableMarkup = n.getDouble("sellcraftableMarkup", sellcraftableMarkup / 100) * 100; + woolsellweight = n.getBoolean("woolsellweight", woolsellweight); + } + + if ((n = config.getNode("itemStock")) != null) { + useItemStock = n.getBoolean("enabled", useItemStock); + mainShopConfig.stockTablename = n.getString("stockTablename", mainShopConfig.stockTablename); + mainShopConfig.noOverStock = n.getBoolean("noOverStock", mainShopConfig.noOverStock); + String num = n.getString("startStock"); + if (num != null) { + mainShopConfig.startStock = CheckInput.GetLong(num, mainShopConfig.startStock);//CheckInput.GetBigInt(num, startStock).longValue(); + } + num = n.getString("maxStock"); + if (num != null) { + mainShopConfig.maxStock = CheckInput.GetLong(num, mainShopConfig.maxStock);//CheckInput.GetBigInt(num, maxStock).longValue(); + } + num = n.getString("restock"); + if (num != null) { + try { + mainShopConfig.restock = CheckInput.GetBigInt_TimeSpanInSec(num, 'h').longValue(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.WARNING, "restock has an illegal value", ex); + } + + } + } + if ((n = config.getNode("strings")) != null) { + for (String k : config.getKeys("strings")) { + if (stringMap.containsKey(k)) { + stringMap.put(k, n.getString(k, stringMap.get(k))); + } + } + } else { + BetterShopLogger.Log(Level.SEVERE, String.format("strings section missing from configuration file %s", configname)); + } + for (String k : stringMap.keySet()) { + stringMap.put(k, stringMap.get(k). + replace("&&", "\t\n"). // so a '&' can still be used + replace("&", "\u00A7"). + replace("\t\n", "&"). // replace the & + replace("%", "%%")); + } + + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, "Error parsing configuration file", ex); + return false; + } + return true; + } + + public void extractDefaults() { + pluginFolder.mkdirs(); + + + if (!configfile.exists()) { + BetterShopLogger.Log(configname + " not found. Creating new file."); + extractFile( + configfile); + + + } + if (!itemDBFile.exists()) { + extractFile(itemDBFile); + + + } + } + + private void extractFile(File dest) { + extractFile(dest, dest.getName()); + + + } + + private void extractFile(File dest, String fname) { + try { + dest.createNewFile(); + InputStream res = BetterShop.class.getResourceAsStream("/" + fname); + FileWriter tx = new FileWriter(dest); + + + + try { + for (int i = 0; (i = res.read()) > 0;) { + tx.write(i); + } + } finally { + tx.flush(); + tx.close(); + res.close(); + } + } catch (IOException ex) { + BetterShopLogger.Log(Level.SEVERE, "Failed creating new file (" + fname + ")", ex); + + + } + } + + public void setCurrency() { + try { +// if (BetterShop.iConomy != null) { +// String t = BetterShop.iConomy.format(1.); +// defaultCurrency = t.substring(t.indexOf(" ") + 1); +// t = BetterShop.iConomy.format(2.); +// pluralCurrency = t.substring(t.indexOf(" ") + 1); +// } else if (BetterShop.legacyIConomy != null) { +// String t = com.nijiko.coelho.iConomy.iConomy.getBank().format(1.); +// defaultCurrency = t.substring(t.indexOf(" ") + 1); +// t = com.nijiko.coelho.iConomy.iConomy.getBank().format(2.); +// pluralCurrency = t.substring(t.indexOf(" ") + 1); +// } else if (BetterShop.economy != null) { +// defaultCurrency = BetterShop.economy.getMoneyName(); +// pluralCurrency = BetterShop.economy.getMoneyNamePlural(); +// } else if (BetterShop.essentials != null) { +// File conf = new File(BetterShop.essentials.getDataFolder(), "config.yml"); +// if (conf.exists()) { +// Configuration config = new Configuration(conf); +// config.load(); +// defaultCurrency = config.getString("currency-name", defaultCurrency); +// pluralCurrency = config.getString("currency-name-plural", pluralCurrency); +// } +// } + String eco = BSEcon.economyMethod.getName(); + + + if (eco.equalsIgnoreCase("iConomy") + || eco.equalsIgnoreCase("BOSEconomy")) { + String t = BSEcon.format(1.); + defaultCurrency = t.substring(t.indexOf(' ') + 1); + t = BSEcon.format(2.); + pluralCurrency = t.substring(t.indexOf(' ') + 1); + + + } else if (eco.equalsIgnoreCase("Essentials")) { + File conf = new File("Essentials", "config.yml"); + + + if (conf.exists()) { + Configuration config = new Configuration(conf); + config.load(); + defaultCurrency = config.getString("currency-name", defaultCurrency); + pluralCurrency = config.getString("currency-name-plural", pluralCurrency); + + + } + } + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, "Error Extracting Currency Name", e); + + + } + } + + public boolean useMySQL() { + return databaseType == DBType.MYSQL; + + + } + + public boolean useFlatfile() { + return databaseType == DBType.FLATFILE; + + + } + + public boolean useGlobalCommandShop() { + return commandShopMode == CommandShopMode.GLOBAL || commandShopMode == CommandShopMode.BOTH; + + + } + + public boolean useRegionCommandShop() { + return commandShopMode == CommandShopMode.REGIONS || commandShopMode == CommandShopMode.BOTH; + + + } + + public boolean useCommandShop() { + return commandShopMode != CommandShopMode.NONE; + + + } + + public boolean useCommandShopGlobal() { + return commandShopMode == CommandShopMode.BOTH; + + + } + + public String getActiveSignColor() { + return activeSignColor; + + + } + + public String getCustomErrorMessage() { + return customErrorMessage; + + + } + + public String getSpoutKey() { + return spoutKey; + + + } + + public void setSpoutKey(String spoutKey) { + if (!this.spoutKey.equals(spoutKey)) { + this.spoutKey = spoutKey; + + + if (BetterShop.keyListener != null) { + BetterShop.keyListener.reloadKey(); + + + } + } + } + + public String getString(String key) { + String ret = stringMap.get(key); + + + if (ret == null) { + BetterShopLogger.Log(Level.WARNING, String.format("%s missing from configuration file", key)); + ret = "(\"" + key + "\" is Missing)"; + + + } + return ret; + + + } + + public boolean hasString(String key) { + String ret = stringMap.get(key); + + + if (ret == null) { + BetterShopLogger.Log(Level.WARNING, String.format("%s missing from configuration file", key)); + + + return false; + + + } + return true; + + + } + + public String currency() { + return defaultCurrency; + + + } + + public String currency(boolean plural) { + return plural ? pluralCurrency : defaultCurrency; + + + } + + String b(boolean b) { + return b ? "1" : "0"; + + + } + + public String condensedSettings() { + return b(checkUpdates) + "," + + pagesize + "," + b(publicmarket) + "," + b(allowbuyillegal) + "," + + b(usemaxstack) + "," + b(buybacktools) + "," + b(buybackenabled) + "," + + b(hideHelp) + "," + b(sendLogOnError) + "," + b(sendAllLog) + "," + + sortOrder.size() + ",'" + mainShopConfig.tableName + "'," + databaseType + "," + + tempCacheTTL + "," + useDBCache + "," + priceListLifespan + "," + + logUserTransactions + "," + userTansactionLifespan + + ",'" + mainShopConfig.transLogTablename + "'," + + logTotalTransactions + ",'" + mainShopConfig.recordTablename + "'," + + useItemStock + ",'" + mainShopConfig.stockTablename + "'," + b(mainShopConfig.noOverStock) + "," + + mainShopConfig.startStock + "," + mainShopConfig.maxStock + "," + mainShopConfig.restock + "," + + useDynamicPricing + "," + sellcraftables + "," + + String.format("%2.3f", sellcraftableMarkup) + "," + + b(woolsellweight) + "," + + spoutEnabled + "," + spoutKey + "," + + largeSpoutMenu + "," + spoutUsePages + "," + + spoutCatCustomSort + "," + spoutCategories.name(); + + + } + + public static int indexOf(String array[], String search) { + if (array != null && array.length > 0) { + for (int i = array.length - 1; i + >= 0; + --i) { + if (array[i].equals(search)) { + return i; + + + } + } + } + return -1; + + + } + + public static int indexOfIgnoreCase(String array[], String search) { + for (int i = array.length - 1; i + >= 0; + --i) { + if (array[i].equalsIgnoreCase(search)) { + return i; + } + } + return -1; + } + + static boolean configHasNode(Configuration config, String[] nodes) { + for (String n : nodes) { + if (config.getProperty(n) != null) { + return true; + } + } + return false; + } +} diff --git a/src/me/jascotty2/bettershop/BSEcon.java b/src/me/jascotty2/bettershop/BSEcon.java new file mode 100644 index 0000000..d928a32 --- /dev/null +++ b/src/me/jascotty2/bettershop/BSEcon.java @@ -0,0 +1,441 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: handler for economy events + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import com.nijikokun.register_1_5.payment.Method; +import com.nijikokun.register_1_5.payment.Methods; +import java.util.Map.Entry; +import me.jascotty2.bettershop.enums.EconMethod; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import net.milkbowl.vault.Vault; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class BSEcon implements Listener { + + protected static Method economyMethod = null; + protected static Methods _econMethods = new Methods(); + protected static String methodName = null; + protected static Economy vaultEcon = null; + // iconomy seems to throw alot of errors... + // this is to only display one + static boolean _pastBalanceErr = false; + static BetterShop plugin; + final PluginManager pm; + + public BSEcon(BetterShop plugin) { + BSEcon.plugin = plugin; + pm = plugin.getServer().getPluginManager(); + if (setupEconomy()) { + methodName = vaultEcon.getName(); + BetterShopLogger.Log("Using " + methodName + " (via Vault) for economy"); + } + Methods.setMethod(pm); + } + + private boolean setupEconomy() { + Plugin v = plugin.getServer().getPluginManager().getPlugin("Vault"); + if (!(v instanceof Vault)) { + return false; + } + RegisteredServiceProvider rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + vaultEcon = rsp.getProvider(); + return vaultEcon != null; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPluginEnable(PluginEnableEvent event) { + if (vaultEcon != null) { + return; + } + if (!Methods.hasMethod() && Methods.setMethod(plugin.getServer().getPluginManager())) { + economyMethod = Methods.getMethod(); + methodName = economyMethod.getName() + " v" + economyMethod.getVersion(); + BetterShopLogger.Log("Using " + methodName + " for economy"); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPluginDisable(PluginDisableEvent event) { + if (vaultEcon != null) { + return; + } + // Check to see if the plugin thats being disabled is the one we are using + if (_econMethods != null && Methods.hasMethod() && Methods.checkDisabled(event.getPlugin())) { + economyMethod = null; + methodName = null; + Methods.reset(); + BetterShopLogger.Log(" Economy Plugin was disabled."); + } + } + + public static boolean active() { + return BetterShop.config.econ != EconMethod.AUTO || vaultEcon != null || economyMethod != null; + } + + public static String getMethodName() { + if (BetterShop.config.econ == EconMethod.AUTO) { + return methodName; + } + if (BetterShop.config.econ == EconMethod.BULTIN) { + return "BettershopEcon"; + } + return "Experience"; + } + + public static boolean hasAccount(Player pl) { + return pl != null && (BetterShop.config.econ != EconMethod.AUTO + || (vaultEcon != null && vaultEcon.hasAccount(pl.getName())) + || (economyMethod != null && economyMethod.hasAccount(pl.getName()))); + } + + public static boolean canAfford(Player pl, double amt) { + if (BetterShop.config.econ != EconMethod.AUTO) { + return pl != null ? getBalance(pl) >= amt : false; + } + return pl != null ? getBalance(pl.getName()) >= amt : false; + } + + public static double getBalance(Player pl) { + if (pl == null) { + return 0; + } else if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + return pl.getExp(); + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + return pl.getTotalExperience(); + } + return pl == null ? 0 : getBalance(pl.getName()); + } + + public static double getBalance(String playerName) { + if (playerName == null) { + return 0; + } else if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + Player p = plugin.getServer().getPlayerExact(playerName); + return p == null ? 0 : p.getExp(); + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + Player p = plugin.getServer().getPlayerExact(playerName); + return p == null ? 0 : p.getTotalExperience(); + } + try { + if (vaultEcon != null && vaultEcon.hasAccount(playerName)) { + return vaultEcon.getBalance(playerName); + } else if (economyMethod != null && economyMethod.hasAccount(playerName)) { + return economyMethod.getAccount(playerName).balance(); + } + } catch (Exception e) { + if (!_pastBalanceErr) { + BetterShopLogger.Severe("Error looking up player balance \n" + + "(this error will only show once)", e); + _pastBalanceErr = true; + } + } + return 0; + } + + public static void addMoney(Player pl, double amt) { + if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + pl.setExp(pl.getExp() + (float) amt); + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + pl.setTotalExperience(pl.getTotalExperience() + (int) amt); + } else { + addMoney(pl.getName(), amt); + } + } + + public static void addMoney(String playerName, double amt) { + if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + Player pl = plugin.getServer().getPlayerExact(playerName); + if (pl != null) { + pl.setExp(pl.getExp() + (float) amt); + } + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + Player pl = plugin.getServer().getPlayerExact(playerName); + if (pl != null) { + pl.setTotalExperience(pl.getTotalExperience() + (int) amt); + } + } else if (vaultEcon != null) { + if (!vaultEcon.hasAccount(playerName)) { + // TODO? add methods for creating an account + return; + } +// EconomyResponse r; + if(amt >= 0) { +// r = + vaultEcon.depositPlayer(playerName, amt); + } else { +// r = + vaultEcon.withdrawPlayer(playerName, -amt); + } +// System.out.println(r.type); +// System.out.println(r.errorMessage); +// System.out.println(r.amount); +// System.out.println(r.balance); + } else if (economyMethod != null) { + if (!economyMethod.hasAccount(playerName)) { + // TODO? add methods for creating an account + return; + } + economyMethod.getAccount(playerName).add(amt); + } + } + + public static void subtractMoney(Player pl, double amt) { + if (pl != null) { + if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + if (pl.getExp() > (int) amt) { + pl.setExp(pl.getExp() - (float) amt); + } else { + pl.setExp(0); + } + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + if (pl.getTotalExperience() > (int) amt) { + pl.setTotalExperience(pl.getTotalExperience() - (int) amt); + } else { + pl.setTotalExperience(0); + } + } else { + subtractMoney(pl.getName(), amt); + } + } + } + + public static void subtractMoney(String playerName, double amt) { + if (BetterShop.config.econ == EconMethod.BULTIN) { + throw new UnsupportedOperationException("Bultin Not supported yet."); + } else if (BetterShop.config.econ == EconMethod.EXP) { + Player pl = plugin.getServer().getPlayerExact(playerName); + if (pl != null) { + if (pl.getExp() > (int) amt) { + pl.setExp(pl.getExp() - (float) amt); + } else { + pl.setExp(0); + } + } + } else if (BetterShop.config.econ == EconMethod.TOTAL) { + Player pl = plugin.getServer().getPlayerExact(playerName); + if (pl != null) { + if (pl.getTotalExperience() > (int) amt) { + pl.setTotalExperience(pl.getTotalExperience() - (int) amt); + } else { + pl.setTotalExperience(0); + } + } + } else if (vaultEcon != null) { + if (!vaultEcon.hasAccount(playerName)) { + // TODO? add methods for creating an account + return; + } + EconomyResponse r; +// System.out.println("subtract(" + playerName + ", " + amt + ")"); + if(amt >= 0) { +// r = + vaultEcon.withdrawPlayer(playerName, amt); + } else { +// r = + vaultEcon.depositPlayer(playerName, -amt); + } + +// System.out.println(r.type); +// System.out.println(r.errorMessage); +// System.out.println(r.amount); +// System.out.println(r.balance); + + } else if (economyMethod != null) { + if (!economyMethod.hasAccount(playerName)) { + // TODO? add methods for creating an account + return; + } + economyMethod.getAccount(playerName).subtract(amt); + } + } + + public static double getPlayerDiscount(Player p) { + if (p != null && !BSPermissions.has(p, "BetterShop.discount.none")) { + double discount = Double.NEGATIVE_INFINITY; + for (Entry g : BetterShop.getSettings().groups.entrySet()) { + if (BSPermissions.has(p, "BetterShop.discount." + g.getKey())) { + if(g.getValue() > discount) discount = g.getValue(); + } + } + if(discount > Double.NEGATIVE_INFINITY) return discount; + } + return 0; + } + + public static boolean credit(Player player, double amount) { + if (amount <= 0) { + // changed: don't attempt to debit if credit amount is negative + return amount == 0;// || debit(player, -amount); + } + if (BSEcon.active()) { + try { + if (bankTransaction(player.getName(), amount)) { + return true; + } + } catch (Exception ex) { + BetterShopLogger.Severe("Failed to credit player", ex); + return true; + } + BetterShopLogger.Severe("Failed to credit player"); + // something seems to be wrong with iConomy: reload it +// BetterShopLogger.Log(Level.SEVERE, "Failed to credit player: attempting iConomy reload", false); +// if (reloadIConomy(player.getServer())) { +// try { +// if (bankTransaction(player.getName(), amount)) { +// return true; +// } +// } catch (Exception ex) { +// } +// } +// BetterShopLogger.Log(Level.SEVERE, "iConomy reload failed to resolve issue.", false); + } else { + BetterShopLogger.Severe("Failed to credit player: no economy plugin"); + return false; + } + return true; + } + + public static boolean debit(Player player, double amount) { + if (amount <= 0) { + // changed: don't attempt to credit if debit amount is negative + return amount == 0;// || credit(player, -amount); + } else if (getBalance(player) < amount) { + return false; + } + if (BSEcon.active()) { + try { + if (bankTransaction(player.getName(), -amount)) { + return true; + } + } catch (Exception ex) { + BetterShopLogger.Severe("Failed to debit player", ex); + return true; + } + BetterShopLogger.Severe("Failed to debit player"); + + // something seems to be wrong with iConomy: reload it +// BetterShopLogger.Log(Level.SEVERE, "Failed to debit player: attempting iConomy reload", false); +// if (reloadIConomy(player.getServer())) { +// try { +// if (bankTransaction(player.getName(), -amount)) { +// return true; +// } +// } catch (Exception ex) { +// } +// } +// BetterShopLogger.Log(Level.SEVERE, "iConomy reload failed to resolve issue.", false); + } else { + BetterShopLogger.Severe("Failed to debit player: no economy plugin"); + return false; + } + return true; + } + + private static boolean bankTransaction(String player, double amount) { + // don't allow account to go negative + double preAmt = BSEcon.getBalance(player); + if (amount > 0 || preAmt >= -amount) { + BSEcon.addMoney(player, amount); + if (BetterShop.config.econ == EconMethod.AUTO + && BetterShop.getSettings().BOSBank != null + && !BetterShop.getSettings().BOSBank.trim().isEmpty() + && hasBank(BetterShop.getSettings().BOSBank)) { + if (economyMethod != null) { + BSEcon.addMoney(BetterShop.getSettings().BOSBank, -amount); + } else if (vaultEcon != null) { + if (amount < 0) { + vaultEcon.bankWithdraw(BetterShop.getSettings().BOSBank, -amount); + } else { + vaultEcon.bankDeposit(BetterShop.getSettings().BOSBank, -amount); + } + } + } + return BSEcon.getBalance(player) != preAmt; + } + return false; + } + + public static String format(double amt) { + try { + if (vaultEcon != null) { + return vaultEcon.format(amt); + } else if (economyMethod != null) { + return economyMethod.format(amt); + } + return String.format("%.2f", amt) + " " + + (amt > 1 || amt < 1 ? BetterShop.getSettings().pluralCurrency + : BetterShop.getSettings().defaultCurrency); + } catch (Exception ex) { + BetterShopLogger.Warning("Error Formatting Currency", ex); + } + return String.format("%.2f", amt); + } + + public static boolean hasBank(String bank) { +// return economyMethod != null +// ? economyMethod.hasBanks() && economyMethod.hasBank(bank) +// : econ != null ? econ.hasBankSupport() && econ.getBanks().contains(bank) : false; + + if (economyMethod != null) { + return economyMethod.hasBanks() && economyMethod.hasBank(bank); + } else if (vaultEcon != null && vaultEcon.hasBankSupport()) { + return vaultEcon.bankBalance(bank).transactionSuccess(); + } + return false; + } +// +// static boolean reloadIConomy(Server serv) { +// try { +// PluginManager m = serv.getPluginManager(); +// Plugin icon = m.getPlugin("iConomy"); +// if (icon != null) { +// m.disablePlugin(icon); +// m.enablePlugin(icon); +// +// return true; +// } +// } catch (Exception ex) { +// BetterShopLogger.Log(Level.SEVERE, "Error reloading iConomy", ex); +// } +// return false; +// } +} // end class BSEcon + diff --git a/src/me/jascotty2/bettershop/BSPermissions.java b/src/me/jascotty2/bettershop/BSPermissions.java new file mode 100644 index 0000000..e987d58 --- /dev/null +++ b/src/me/jascotty2/bettershop/BSPermissions.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for handling permissions checks + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import com.nijikokun.bukkit.Permissions.Permissions; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.util.Str; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * @author jacob + */ +public class BSPermissions { + + public static Permissions permissionsPlugin = null; + public static Permission vaultPerms = null; + + public static boolean hasPermission(CommandSender player, BetterShopPermission node) { + return hasPermission(player, node.toString(), false); + } + + public static boolean hasPermission(CommandSender player, BetterShopPermission node, boolean notify) { + return hasPermission(player, node.toString(), notify); + } + + public static boolean hasPermission(CommandSender player, String node) { + return hasPermission(player, node, false); + } + + public static boolean hasPermission(CommandSender player, String node, boolean notify) { + if (player == null || player.isOp() || !(player instanceof Player) + || node == null || node.length() == 0) { // ops override permission check (double-check is a Player) + return true; + } + if (has((Player) player, node)) { + return true; + } else if (notify) { + //PermDeny(player, node); + BSutils.sendMessage(player, + BetterShop.getSettings().getString("permdeny").replace("", node)); + } + return false; + } + + public static boolean has(Player player, String node) { + try { + if (vaultPerms != null) { + return vaultPerms.has(player, node); + } else if (permissionsPlugin != null) { + return permissionsPlugin.getHandler().has(player, node); + } +// System.out.println("no perm: checking superperm for " + player.getName() + ": " + node); +// System.out.println(player.hasPermission(node)); +// for(PermissionAttachmentInfo i : player.getEffectivePermissions()){ +// System.out.println(i.getPermission()); +// } + if (player.hasPermission(node)) { + return true; + } else if (!node.contains("*") && Str.count(node, '.') >= 2) { +// System.out.println("Checking for " + node.substring(0, node.lastIndexOf('.') + 1) + "* : " +// + player.hasPermission(node.substring(0, node.lastIndexOf('.') + 1) + "*")); + return player.hasPermission(node.substring(0, node.lastIndexOf('.') + 1) + "*"); + } +// System.out.println("no permission"); + return false; + } catch (Exception e) { + BetterShopLogger.Severe(e); + } + return node.length() < 16 // if invalid node, assume true + || (!node.substring(0, 16).equalsIgnoreCase("BetterShop.admin") // only ops have access to .admin + && !node.substring(0, 19).equalsIgnoreCase("BetterShop.discount")); + } + +} // end class BSPermissions diff --git a/src/me/jascotty2/bettershop/BSPluginListener.java b/src/me/jascotty2/bettershop/BSPluginListener.java new file mode 100644 index 0000000..bfbeb46 --- /dev/null +++ b/src/me/jascotty2/bettershop/BSPluginListener.java @@ -0,0 +1,147 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: adds & removes registered plugins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import me.jascotty2.bettershop.commands.HelpCommands; +import me.jascotty2.bettershop.spout.SpoutKeyListener; +import me.jascotty2.bettershop.spout.SpoutPopupListener; +import me.jascotty2.bettershop.utils.BetterShopLogger; + +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.java.JavaPlugin; + +import com.jascotty2.minecraftim.MinecraftIM; +import com.nijikokun.bukkit.Permissions.Permissions; +import me.taylorkelly.help.Help; +import net.milkbowl.vault.Vault; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.getspout.spout.Spout; + +class BSPluginListener implements Listener { + + BetterShop shop; + + public BSPluginListener(BetterShop plugin) { + shop = plugin; + PluginManager pm = plugin.getServer().getPluginManager(); + checkVaultPermissions(pm.getPlugin("Vault")); + checkPermissions(pm.getPlugin("Permissions")); + checkSpout(pm.getPlugin("Spout")); + checkHelp(pm.getPlugin("Help")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPluginEnable(PluginEnableEvent event) { + if (event.getPlugin().isEnabled()) { // double-checking if enabled + String pName = event.getPlugin().getDescription().getName(); + if (pName.equals("Help")) { + checkHelp(event.getPlugin()); + } else if (pName.equals("Permissions")) { + checkPermissions(event.getPlugin()); + } else if (pName.equals("Spout")) { + checkSpout(event.getPlugin()); + } else { + BetterShop.economy.onPluginEnable(event); + } + } + } + + public final void checkHelp(Plugin p) { + if (!HelpCommands.helpPluginEnabled && p instanceof Help) { + HelpCommands.registerHelp(p); + BetterShopLogger.Info("'Help' support enabled."); + } + } + + public final void checkVaultPermissions(Plugin p) { + if (BSPermissions.vaultPerms == null && p instanceof Vault) { + RegisteredServiceProvider rsp = p.getServer().getServicesManager().getRegistration(Permission.class); + if(rsp != null) { + BSPermissions.vaultPerms = rsp.getProvider(); + if(BSPermissions.vaultPerms != null) { + BetterShopLogger.Log("Attached to " + BSPermissions.vaultPerms.getName() + " via Vault."); + } + } + } + } + + public final void checkPermissions(Plugin p) { + if (BSPermissions.vaultPerms == null && + BSPermissions.permissionsPlugin == null && p instanceof Permissions) { + BSPermissions.permissionsPlugin = (Permissions) p; + BetterShopLogger.Log("Attached to Permissions."); + } + } + + public final void checkSpout(Plugin p) { + if (BetterShop.keyListener == null && p instanceof Spout) { + + BetterShopLogger.Log("Spout Found! :)"); +// File spFile = new File(test.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(). +// replace("%20", " ").replace("%25", "%")); + try { + Plugin bp = BetterShop.getPlugin(); + //bp.super.getClassLoader().loadClass(event.getPlugin().getDescription().getMain()); + //JavaPlugin.class.getClassLoader().loadClass(event.getPlugin().getDescription().getMain()); + ((JavaPlugin) bp).getClass().getClassLoader().loadClass(p.getDescription().getMain()); + BetterShop.keyListener = new SpoutKeyListener(); + BetterShop.buttonListener = new SpoutPopupListener(); + + // spout listeners + PluginManager pm = shop.getServer().getPluginManager(); + pm.registerEvents(BetterShop.keyListener, shop); + pm.registerEvents(BetterShop.buttonListener, shop); + + BetterShop.chestShop.registerSpout(true); + + } catch (ClassNotFoundException ex) { + BetterShopLogger.Severe("Error loading Spout!", ex); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPluginDisable(PluginDisableEvent event) { + if (!event.getPlugin().isEnabled()) { + String pName = event.getPlugin().getDescription().getName(); + if (pName.equals("Help")) { + HelpCommands.helpPluginEnabled = false; + } else if (pName.equals("Permissions")) { + if (event.getPlugin() instanceof Permissions) { + BSPermissions.permissionsPlugin = (Permissions) event.getPlugin(); + BetterShopLogger.Log("Permissions disabled."); + } + } else if (pName.equals("Spout")) { + BetterShop.keyListener = null; + BetterShop.buttonListener = null; + BetterShop.chestShop.registerSpout(false); + BetterShopLogger.Log("Spout disabled."); + } else { + BetterShop.economy.onPluginDisable(event); + } + } + } +} // end class BSPluginListener + diff --git a/src/me/jascotty2/bettershop/BSutils.java b/src/me/jascotty2/bettershop/BSutils.java new file mode 100644 index 0000000..8bd9b11 --- /dev/null +++ b/src/me/jascotty2/bettershop/BSutils.java @@ -0,0 +1,267 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: generic methods that i haven't moved to their own classes + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.ItemStockEntry; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import java.util.ArrayList; +import java.util.List; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.MinecraftChatStr; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; +//import org.bukkit.Server; +//import org.bukkit.plugin.Plugin; +//import org.bukkit.plugin.PluginManager; +//import org.bukkit.permissions.PermissionAttachmentInfo; + +public class BSutils { + + public static boolean anonymousCheck(CommandSender sender) { + if (!(sender instanceof Player)) { + sendMessage(sender, "Cannot execute that command, I don't know who you are!"); + return true; + } else { + return false; + } + } + + public static void sendFormttedMessage(Player player, String key, String item, int amt, double total) { + + BSutils.sendMessage(player, BetterShop.getSettings().getString(key). + replace("", item). + replace("", Integer.toString(amt)). + replace("", String.format("%.2f", total / amt)). + replace("", String.format("%.2f", total)). + replace("", BetterShop.getSettings().currency()). + replace("", BSEcon.format(total))); + //price + if (BetterShop.getSettings().publicmarket && BetterShop.getSettings().hasString("public" + key)) { + BSutils.broadcastMessage(player, BetterShop.getSettings().getString("public" + key). + replace("", item). + replace("", String.valueOf(amt)). + replace("", String.format("%.2f", total / amt)). + replace("", String.format("%.2f", total)). + replace("", BetterShop.getSettings().currency()). + replace("", BSEcon.format(total)). + replace("", player.getDisplayName()), + false); + } + } + + public static void sendMessage(CommandSender player, String s) { + if (player != null && s != null) { + if (s.contains("\n")) { + String lns[] = s.split("\n"); + player.sendMessage(BetterShop.getSettings().getString("prefix") + lns[0]); + for (int i = 1; i < lns.length; ++i) { + player.sendMessage(lns[i]); + } + } else { + player.sendMessage(BetterShop.getSettings().getString("prefix") + s); + } + } + } + + public static void sendMessage(CommandSender player, String s, boolean isPublic) { + if (!isPublic) { + sendMessage(player, s); + } else if (player != null) { + broadcastMessage(player, s); + } + } + + public static void broadcastMessage(CommandSender player, String s) { + if (player != null) { + if (s.contains("\n")) { + String lns[] = s.split("\n"); + player.getServer().broadcastMessage(BetterShop.getSettings().getString("prefix") + lns[0]); + for (int i = 1; i < lns.length; ++i) { + player.getServer().broadcastMessage(lns[i]); + } + } else { + player.getServer().broadcastMessage(BetterShop.getSettings().getString("prefix") + s); + } + } + BetterShopLogger.Log("(public announcement) " + s.replaceAll("\\\u00A7.", "")); + } + + public static void broadcastMessage(CommandSender player, String s, boolean includePlayer) { + if (player != null) { + if (includePlayer) { + broadcastMessage(player, s); + } else { + String name = player instanceof Player ? ((Player) player).getDisplayName() : ""; + for (Player p : player.getServer().getOnlinePlayers()) { + if (!p.getDisplayName().equals(name)) { + //player.getServer().broadcastMessage(BetterShop.getConfig().getString("prefix") + s); + //p.sendMessage(BetterShop.getConfig().getString("prefix") + s); + sendMessage(p, s); + } + } + BetterShopLogger.Log("(public announcement) " + MinecraftChatStr.uncoloredStr(s)); + } + } + } + + /** + * all items in the user's inventory + * does not run check for tools, so tools with damage are returned separately + * @param player Player to check + * @param onlyInv if the lower 9 slots should be checked or not + * @return + */ + public static ArrayList getTotalInventory(Player player, boolean onlyInv) { + if (player == null) { + return null; + } + ArrayList inv = new ArrayList(); + + ItemStack[] its = player.getInventory().getContents(); + for (int i = (onlyInv ? 9 : 0); i <= 35; ++i) { + if (its[i] != null && its[i].getAmount() > 0) { + ItemStockEntry find = new ItemStockEntry(its[i]); + int pos = inv.indexOf(find); + if (pos >= 0) { + inv.get(pos).AddAmount(its[i].getAmount()); + } else { + inv.add(find); + } + } + } + + return inv; + } + + public static ArrayList getTotalInventory(Player player, boolean onlyInv, JItem toFind) { + if (toFind == null) { + return getTotalInventory(player, onlyInv); + }// else + return getTotalInventory(player, onlyInv, new JItem[]{toFind}); + } + + public static ArrayList getTotalInventory(Player player, boolean onlyInv, JItem[] toFind) { + if (toFind == null || toFind.length == 0) { + return getTotalInventory(player, onlyInv); + } else if (player == null) { + return null; + } + ArrayList inv = new ArrayList(); + + ItemStack[] its = player.getInventory().getContents(); + for (int i = (onlyInv ? 9 : 0); i <= 35; ++i) { + if (its[i] != null && its[i].getAmount() > 0) { + for (JItem it : toFind) { + if (it != null && its[i] != null && it.equals(its[i])) { + ItemStockEntry find = new ItemStockEntry(its[i]); + int pos = inv.indexOf(find); + if (pos >= 0) { + inv.get(pos).AddAmount(its[i].getAmount()); + } else { + inv.add(find); + } + break; + } + } + } + } + return inv; + } + + public static ArrayList getTotalInventory(Player player, boolean onlyInv, List toFind) { + if (toFind == null) { + return getTotalInventory(player, onlyInv); + } else if (player == null) { + return null; + } + ArrayList inv = new ArrayList(); + if (toFind.isEmpty()) { + return inv; + } + ItemStack[] its = player.getInventory().getContents(); + for (int i = (onlyInv ? 9 : 0); i <= 35; ++i) { + if (its[i] != null && its[i].getAmount() > 0) { + for (ItemStockEntry it : toFind) { + if (it != null && its[i] != null && it.equals(its[i])) { + int pos = inv.indexOf(it); + if (pos >= 0) { + inv.get(pos).AddAmount(its[i].getAmount()); + } else { + inv.add(new ItemStockEntry(it)); + } + break; + } + } + } + } + return inv; + } + /* + public static ArrayList getTotalSellableInventory(Player player, boolean onlyInv) { + ArrayList inv = new ArrayList(); + try { + ItemStack[] its = player.getInventory().getContents(); + for (int i = (onlyInv ? 9 : 0); i <= 35; ++i) { + if (its[i].getAmount() > 0 && BetterShop.getPricelist().isForSale(its[i])) { + ItemStockEntry find = new ItemStockEntry(its[i]); + int pos = inv.indexOf(find); + if (pos >= 0) { + inv.get(pos).AddAmount(its[i].getAmount()); + } else { + inv.add(find); + } + } + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + return null; + } + return inv; + }//*/ + + public static int amtHas(Player player, JItem toBuy) { + int amt = 0; + if (!toBuy.isEntity()) { + PlayerInventory inv = player.getInventory(); + // don't search armor slots + for (int i = 0; i <= 35; ++i) { + ItemStack it = inv.getItem(i); + if (toBuy.equals(it)) { + amt += it.getAmount(); + } + } + } + return amt; + } + + public static int amtCanHold(Player player, JItem toBuy) { + if (!toBuy.isEntity()) { + return ItemStackManip.amountCanHold(player.getInventory().getContents(), + toBuy, !BetterShop.getSettings().usemaxstack); + } else { + return BetterShop.getSettings().maxEntityPurchase; + } + } + +} diff --git a/src/me/jascotty2/bettershop/BetterShop.java b/src/me/jascotty2/bettershop/BetterShop.java new file mode 100644 index 0000000..49cd0ea --- /dev/null +++ b/src/me/jascotty2/bettershop/BetterShop.java @@ -0,0 +1,429 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: Global Shop System for Minecraft + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import java.io.IOException; +import java.sql.SQLException; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.shop.BSTransactionLog; +import me.jascotty2.bettershop.shop.BSItemStock; +import me.jascotty2.bettershop.shop.BSPriceList; +import me.jascotty2.bettershop.spout.SpoutKeyListener; +import me.jascotty2.bettershop.spout.SpoutPopupListener; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.bettershop.commands.BSCommandManager; +import me.jascotty2.bettershop.commands.HelpCommands; +import me.jascotty2.bettershop.regionshops.RegionShopManager; +import me.jascotty2.bettershop.signshop.BSSignShop; +import me.jascotty2.bettershop.chestshop.BSChestShop; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.bukkit.Server; +import org.bukkit.ChatColor; +import org.bukkit.Location; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.Collection; + +import me.jascotty2.bettershop.enums.ShopMethod; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.CreatureItem.EntityListen; +import me.jascotty2.lib.bukkit.commands.CommandException; +import me.jascotty2.lib.bukkit.commands.CommandPermissionsException; +import me.jascotty2.lib.bukkit.commands.CommandUsageException; +import me.jascotty2.lib.bukkit.commands.MissingNestedCommandException; +import me.jascotty2.lib.bukkit.commands.WrappedCommandException; +import me.jascotty2.lib.util.ArrayManip; +import me.jascotty2.lib.util.Str; +import org.mcstats.Metrics; + +/** + * BetterShop for Bukkit + */ +public class BetterShop extends JavaPlugin { + + protected static Plugin bettershopPlugin = null; + protected final static BSConfig config = new BSConfig(); + protected final static RegionShopManager shopManager = new RegionShopManager(); + protected static BSSignShop signShop = null; + protected static BSChestShop chestShop = null; + protected static BSEcon economy; + protected static TransactionHandler shopHandler; + private BSPluginListener pListener = null; + // for animal/monster purchases + public final EntityListen entityListener = new EntityListen(); + private final static BSCommandManager commandManager = new BSCommandManager(); + protected static String lastCommand = ""; + // for spout-related classes + protected static SpoutKeyListener keyListener = null; + protected static SpoutPopupListener buttonListener = null; + + @Override + public void onEnable() { + bettershopPlugin = this; + PluginDescriptionFile pdfFile = this.getDescription(); + BetterShopLogger.init(this.getLogger()); + BetterShopLogger.setUsePrefix(false); + + config.extractDefaults(); + // ready items db (needed for pricelist, sorting in config, item lookup, + // ...) + try { + JItemDB.load(BSConfig.itemDBFile); + // Log("Itemsdb loaded"); + } catch (Exception e) { + BetterShopLogger.Severe("cannot load items db: closing plugin", e); + this.setEnabled(false); + return; + } + + config.load(); + + economy = new BSEcon(this); + shopHandler = new TransactionHandler(economy, shopManager); + + if (shopManager.load() > 0) { + BetterShopLogger.Severe("Error while enabling Shop"); + } + signShop = new BSSignShop(this); + + if (config.signShopEnabled && !signShop.load()) { + BetterShopLogger.Severe("cannot load sign shop database"); + } + if (config.signShopEnabled && config.signWEprotection) { + signShop.startProtecting(); + } + + signShop.registerEvents(); + + + chestShop = new BSChestShop(this); + + if (config.chestShopEnabled && !chestShop.load()) { + BetterShopLogger.Severe("cannot load chest shop database"); + } + + chestShop.registerEvents(); + + + // for monster purchasing + PluginManager pm = getServer().getPluginManager(); + pm.registerEvents(entityListener, this); + + // monitor plugins - if any are enabled/disabled by a plugin manager + pListener = new BSPluginListener(this); + pm.registerEvents(pListener, this); + + BetterShopLogger.Info(pdfFile.getName() + " version " + + pdfFile.getVersion() + " is enabled!"); + + try { + Metrics metrics = new Metrics(this); + metrics.start(); + } catch (IOException e) { + // Failed to submit the stats :-( + } + } + + @Override + public void onDisable() { + // NOTE: All registered events are automatically unregistered when a plugin is disabled + try { + lastCommand = "(disabling)"; + + if (signShop != null) { + signShop.save(); + signShop.stopProtecting(); + } + signShop = null; + + if (chestShop != null) { + chestShop.save(); + chestShop.closeAllChests(); + } + chestShop = null; + + shopManager.closeAll(); + + keyListener = null; + buttonListener = null; + + BetterShopLogger.CloseCommandLog(); + + BetterShopLogger.Fine("disabled"); + + } catch (Throwable t) { + BetterShopLogger.Severe("error disabling..", t); + } + } + + @Override + public boolean onCommand(CommandSender sender, Command command, + String commandLabel, String[] args) { + String commandName = command.getName().toLowerCase(), argStr = Str.concatStr(args, " "); + if (!commandManager.hasCommand(commandName)) { + // No command found! + return false; + } + lastCommand = (sender instanceof Player ? "player:" : "console:") + commandName + " " + argStr; + + if (sender instanceof Player + && (!shopManager.locationHasShop(((Player) sender).getLocation()) + && (Str.isIn(commandName, + new String[]{"shopbuy", "shopbuyall", "shopbuystack", + "shopsell", "shopsellall", "shopsellstack", + "shoplist", "shopitems", "shopcheck", "shoplistkits" + /*, "shopadd", + * "shopremove" + */}) + && !commandShopEnabled(((Player) sender).getLocation())))) { + BSutils.sendMessage(sender, config.getString("regionShopDisabled")); + return true; + } + + if (config.logCommands) { + BetterShopLogger.LogCommand( + sender instanceof Player ? ((Player) sender).getName() : "(console)", + commandLabel + " " + argStr); + } + + if (!BSEcon.active() + && (commandName.contains("buy") + || commandName.contains("sell") + || argStr.contains("buy") || argStr.contains("sell"))) { + BSutils.sendMessage(sender, ChatColor.RED + + "BetterShop is missing a dependency. Check the console."); + BetterShopLogger.Severe("Missing: an economy plugin\nsupported: iConomy 4,5,6, BOSEcon 4,5, MultiCurrency, EssensialsEco"); + return true; + } + + checkRestock(); + + try { + commandManager.execute(sender, commandName, args); + return true; + } catch (CommandPermissionsException e) { + BSutils.sendMessage(sender, + BetterShop.getSettings().getString("permdeny").replace("", e.getNode())); + return true; + } catch (MissingNestedCommandException e) { + //BSutils.sendMessage(sender, ChatColor.RED + e.getMessage()); + BSutils.sendMessage(sender, ChatColor.RED + e.getUsage()); + } catch (CommandUsageException e) { + BSutils.sendMessage(sender, ChatColor.RED + e.getMessage()); + BSutils.sendMessage(sender, ChatColor.RED + e.getUsage()); + } catch (WrappedCommandException e) { + Throwable t = e.getCause(); + if(t instanceof SQLException){ + BetterShopLogger.Severe("Problem with MySQL Connection", t); + } else if(t instanceof IOException){ + BetterShopLogger.Severe("Problem with File Access", t); + } else { + BetterShopLogger.Severe("Error executing a command", t); + } + BSutils.sendMessage(sender, ChatColor.RED + "Problem Executing Command!"); + return true; + } catch (CommandException e) { + //BetterShopLogger.Warning(e); + BSutils.sendMessage(sender, ChatColor.RED + /*"Problem Executing Command!"*/ e.getMessage()); + } catch (Exception e) { + BetterShopLogger.Severe("Unexpected Error executing a command", e); + BSutils.sendMessage(sender, ChatColor.RED + "Unexpected Error executing command"); + return true; + } catch (Throwable t) { + BetterShopLogger.Severe("Unexpected Error in BetterShop", t); + BSutils.sendMessage(sender, ChatColor.RED + "Unexpected Error executing command"); + return true; + } + //TODO: help can get help from any command. + //HelpCommands.help(sender, ArrayManip.arrayConcat(new String[]{command.getName()}, args)); + return true; + } + + public static void setLastCommand(String lastCommand) { + StackTraceElement stm[] = (new Exception()).getStackTrace(); + if (stm[1].toString().startsWith(BetterShop.class.getPackage().getName())) { + BetterShop.lastCommand = lastCommand; + } else { + BetterShopLogger.Warning("Stranger Attempted to modify last command: " + stm[1].toString()); + } + } + + public static Plugin getPlugin() { + return bettershopPlugin; + } + + public static BSConfig getSettings() { + return config; + } + + public static String getLastCommand() { + return lastCommand; + } + + public static BSPriceList getMainPricelist() { + return shopManager.getShop((String) null).pricelist; + } + + public static BSPriceList getPricelist(Location l) { + return shopManager.getShop(l).pricelist; + } + + public static BSPriceList getPricelist(CommandSender player) { + return getShop(player).pricelist; + } + + public static BSSignShop getSignShop() { + return signShop; + } + + public static BSChestShop getChestShop() { + return chestShop; + } + + public static BSItemStock getStock(Location l) { + return shopManager.getShop(l).stock; + } + + public static BSItemStock getStock(CommandSender player) { + return getShop(player).stock; + } + + public static void checkRestock() { + shopManager.checkRestock(); + } + + public static void restock() { + shopManager.restock(); + } + + public static BSTransactionLog getTransactions(Location l) { + return shopManager.getShop(l).transactions; + } + + public static BSTransactionLog getTransactions(CommandSender player) { + return getShop(player).transactions; + } + + public static Shop getMainShop() { + return shopManager.getShop((String) null); + } + + public static Shop getShop(Location l) { + return shopManager.getShop(l); + } + + public static Shop getShop(String s) { + return shopManager.getShop(s); + } + + public static Shop getShop(CommandSender player) { + return shopManager.getShop(player instanceof Player ? ((Player) player).getLocation() : null); + } + + public static Collection getShops() { + return shopManager.getShops(); + } + + public static RegionShopManager getShopManager() { + return shopManager; + } + + public static boolean commandShopEnabled(Location loc) { + return shopManager.isCommandShopEnabled(loc); + } + + public static boolean spoutEnabled(){ + return buttonListener != null; + } + + public static TransactionHandler.TransactionResult executeTransaction(Player player, PlayerTransation action, ShopMethod method) { + return shopHandler.execute(player, action, method); + } + + public static int reload(CommandSender sender) { + int errors = 0; + if (config.signShopEnabled && signShop.saveDelayActive()) { + if (signShop.save()) { + BSutils.sendMessage(sender, BetterShop.getSignShop().numSigns() + " signs saved."); + } else { + BSutils.sendMessage(sender, ChatColor.RED + "shop signs db save error"); + ++errors; + } + } + + if (config.signShopEnabled && config.tntSignDestroyProtection) { + signShop.stopProtecting(); + } + if (JItemDB.load(BSConfig.itemDBFile)) { + BSutils.sendMessage(sender, JItemDB.size() + " items loaded."); + } else { + BetterShopLogger.Warning("Cannot Load Items db!"); + BSutils.sendMessage(sender, ChatColor.RED + "Item Database Load Error."); + ++errors; + } + + if (!BetterShop.getSettings().load()) { + BSutils.sendMessage(sender, ChatColor.RED + "Config loading error."); + ++errors; + } else { + BSutils.sendMessage(sender, "Config.yml loaded."); + } + + shopManager.closeAll(); + errors += shopManager.load(); + + if (config.signShopEnabled && signShop.load()) { + BSutils.sendMessage(sender, BetterShop.getSignShop().numSigns() + " signs loaded."); + } else { + BSutils.sendMessage(sender, ChatColor.RED + "shop signs db load error"); + ++errors; + } + if (BetterShop.getSettings().signShopEnabled && BetterShop.getSettings().tntSignDestroyProtection) { + BetterShop.getSignShop().startProtecting(); + } + return errors; + } + + public static class ServerReload extends TimerTask { + + Server reload = null; + + public ServerReload(Server s) { + reload = s; + } + + public void start(long wait) { + (new Timer()).schedule(this, wait); + } + + @Override + public void run() { + if (reload != null) { + reload.reload(); + } + } + } +} diff --git a/src/me/jascotty2/bettershop/PlayerTransation.java b/src/me/jascotty2/bettershop/PlayerTransation.java new file mode 100644 index 0000000..e50a789 --- /dev/null +++ b/src/me/jascotty2/bettershop/PlayerTransation.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2012 Jacob Scott + * Description: Structure to hold central transaction details + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; +import me.jascotty2.lib.bukkit.item.JItem; + +public class PlayerTransation { + /** + * player initiating the transaction + */ + public String playername; + /** + * what direction (false == buying the item) + */ + public boolean is_selling; + /** + * how many items trying to buy/sell + */ + public int amount; + /** + * if the item(s) is known + */ + public JItem items[]; + /** + * if the item is being looked up, what the search term is + */ + public String itemSearch; + /** + * if this is a custom request, can define the specific buy/sell price + */ + public float customPrice = -1; +} diff --git a/src/me/jascotty2/bettershop/TransactionHandler.java b/src/me/jascotty2/bettershop/TransactionHandler.java new file mode 100644 index 0000000..1ddee70 --- /dev/null +++ b/src/me/jascotty2/bettershop/TransactionHandler.java @@ -0,0 +1,221 @@ +/** + * Copyright (C) 2012 Jacob Scott + * Description: static methods used when buying/selling + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop; + +import me.jascotty2.bettershop.enums.BetterShopPermission; +import org.bukkit.entity.Player; +import org.bukkit.inventory.PlayerInventory; +import me.jascotty2.bettershop.regionshops.RegionShopManager; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.enums.ShopMethod; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; +import me.jascotty2.lib.bukkit.item.Kit; + +public class TransactionHandler { + + private final BSEcon economy; + private final RegionShopManager shopManager; + + public TransactionHandler(BSEcon economy, RegionShopManager shopManager) { + if (economy == null || shopManager == null) { + throw new IllegalArgumentException("TransactionHandler Constructor cannot be null"); + } + this.economy = economy; + this.shopManager = shopManager; + } + + /** + * execute a shop transaction
+ * note: this method assumes that the player has permission to access the shop at their location + * @param player + * @param action + * @return + */ + public TransactionResult execute(Player player, PlayerTransation action, ShopMethod method) { + if (player == null || action == null) { + throw new IllegalArgumentException("TransactionHandler execute arguments cannot be null"); + } + + // check if has permission to use the shop + if (method != ShopMethod.PLUGIN) { + if (!BSPermissions.hasPermission(player, action.is_selling ? BetterShopPermission.USER_SELL : BetterShopPermission.USER_BUY)) { + return new TransactionResult(BetterShop.getSettings().getString("permdeny").replace("", + (action.is_selling ? BetterShopPermission.USER_SELL : BetterShopPermission.USER_BUY).toString())); + } + // check if the shop is accessable from here + if ((method == ShopMethod.COMMAND || method == ShopMethod.SPOUT) + && !shopManager.locationHasShop(player.getLocation()) + && !shopManager.isCommandShopEnabled(player.getLocation())) { + return new TransactionResult(BetterShop.getSettings().getString("regionShopDisabled")); + } + } + + // lookup item(s) + if (action.items == null || action.items.length == 0) { + if (action.itemSearch == null || action.itemSearch.trim().length() == 0) { + return new TransactionResult("no item to lookup provided"); + } + if (JItemDB.isCategory(action.itemSearch)) { + action.items = JItemDB.getItemsByCategory(action.itemSearch); + } else { + action.items = new JItem[1]; + action.items[0] = JItemDB.findItem(action.itemSearch); + } + if (action.items == null || action.items.length == 0 || action.items[0] == null) { + return new TransactionResult(BetterShop.getSettings().getString("unkitem").replace("", action.itemSearch)); + } + } + TransactionResult result = new TransactionResult(); + + // check if has permission to purchase item + if (!action.is_selling && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL)) { + String cantBuy = ""; + int good = 0; + for (int i = 0; i < action.items.length; ++i) { + if (action.items[i] != null && !action.items[i].IsLegal()) { + if (action.items[i] != null) { + cantBuy += (cantBuy.length() > 0 ? ", " : "") + action.items[i].coloredName(); + action.items[i] = null; + } + } else { + ++good; + } + } + if (!cantBuy.isEmpty()) { + result.addError(BetterShop.getSettings().getString("illegalbuy").replace("", cantBuy)); + } + if (good == 0) { + return result; + } + } + + Shop shop = shopManager.getShop(method == ShopMethod.PLUGIN ? null : player.getLocation()); + + // check if the item(s) are sold/accepted by this shop + try { + String reject = ""; + int good = 0; + for (int i = 0; i < action.items.length; ++i) { + if (action.items[i] != null && !(action.is_selling + ? shop.pricelist.isForSale(action.items[i]) + : shop.pricelist.canBuy(action.items[i]))) { + if (action.items[i] != null) { + reject += (reject.length() > 0 ? ", " : "") + action.items[i].coloredName(); + action.items[i] = null; + } + } else { + ++good; + } + } + if (!reject.isEmpty()) { + result.addError(BetterShop.getSettings().getString( + action.is_selling ? "donotwant" : "notforsale").replace("", reject)); + } + if (good == 0) { + return result; + } + } catch (Exception e) { + result.addError("Unexpected Error while accessing Pricelist: " + e.getMessage()); + return result; + } + + PlayerInventory inv = player.getInventory(); + // now check how many can buy/sell + // invCount becomes the max of any one item that the player can buy + int invCount = 0; + if (action.is_selling) { + for (JItem it : action.items) { + if (it != null && !it.isEntity()) { + // don't count armor slots + int amt = ItemStackManip.count(inv.getContents(), it, 0, 35); + if (amt > invCount) { + invCount = amt; + } + } + } + } else { + // buying + invCount = ItemStackManip.amountCanHold(inv.getContents(), + new Kit(action.items), !BetterShop.getSettings().usemaxstack); + + } + int stockAmt = 0; + // check amount in stock + if (BetterShop.getSettings().useItemStock) { + try { + for (JItem it : action.items) { + if (it != null) { + long st = action.is_selling ? shop.stock.freeStockRemaining(it) : shop.stock.getItemAmount(it); + if (st > stockAmt) { + if (st >= Integer.MAX_VALUE) { + stockAmt = Integer.MAX_VALUE; + break; + } else { + stockAmt = (int) st; + } + } else if (st == -1) { + stockAmt = Integer.MAX_VALUE; + break; + } + } + } + } catch (Exception ex) { + result.addError("Unexpected Error while accessing Shop Stock: " + ex.getMessage()); + return result; + } + } else { + stockAmt = Integer.MAX_VALUE; + } + + int amtCan = 0; + if(action.is_selling) { + // if add player shops, test if the player can afford to buy this many + amtCan = Integer.MAX_VALUE; + } else { + // check if the player can afford to buy this many items + + } + result.all_ok = true; + return result; + } + + public static class TransactionResult { + + public boolean all_ok = false; + public int end_amount; + public float end_price; + public String error_message; + + public TransactionResult() { + } + + public TransactionResult(String errorMessage) { + this.error_message = errorMessage; + } + + public void addError(String error) { + if (error_message == null || error_message.isEmpty()) { + error_message = error; + } else { + error_message += "\n" + error; + } + } + } +} diff --git a/src/me/jascotty2/bettershop/chestshop/BSChestShop.java b/src/me/jascotty2/bettershop/chestshop/BSChestShop.java new file mode 100644 index 0000000..6f6d609 --- /dev/null +++ b/src/me/jascotty2/bettershop/chestshop/BSChestShop.java @@ -0,0 +1,523 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for adding a chest interface to bettershop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.chestshop; + +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.shop.BSPriceList; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; + +import me.jascotty2.lib.bukkit.inventory.ChestManip; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.shop.UserTransaction; + +import net.minecraft.server.v1_7_R1.EntityPlayer; +import net.minecraft.server.v1_7_R1.IInventory; +import net.minecraft.server.v1_7_R1.InventoryLargeChest; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.craftbukkit.v1_7_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; +import me.jascotty2.lib.util.ArrayManip; +import me.jascotty2.lib.util.Str; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +/** + * @author jacob + */ +public class BSChestShop implements Listener { + + final static int chestStrLen = 16; // 1.8 won't allow many chars :( + final static long chestProtectDelay = 5000; + protected final BetterShop plugin; + final ChestDB chestsDB; + final HashMap openPlayers = new HashMap(); + final HashMap openChests = new HashMap(); + final HashMap openChestLocations = new HashMap(); + final ArrayList editActive = new ArrayList(); + public final ChestListener chestChecker; + ChestShopInventoryListenerSpout invListen = null; + + public BSChestShop(BetterShop shop) { + plugin = shop; + chestsDB = new ChestDB(plugin.getServer()); + chestChecker = new ChestListener(plugin, chestsDB); + + } // end default constructor + + public void registerEvents() { + PluginManager pm = plugin.getServer().getPluginManager(); + pm.registerEvents(this, plugin); + + pm.registerEvents(chestChecker, plugin); + chestChecker.startProtect(); + } + + public void registerSpout(boolean enable) { + if (enable) { + if (invListen == null) { + invListen = new ChestShopInventoryListenerSpout(this); + + plugin.getServer().getPluginManager().registerEvents(invListen, plugin); + } + } else if (invListen != null) { + invListen = null; + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.isCancelled() || !BetterShop.getSettings().chestShopEnabled) { + return; + } + Player p = event.getPlayer(); + chestClose(p); + if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + Block b = event.getClickedBlock(); + if (chestsDB.has(b.getLocation())) { + // sanity check + if (!(b.getState() instanceof Chest)) { + chestsDB.remove(b.getLocation()); + return; + } + event.setCancelled(true); + // now check if this user has permission to use chest shops + if (!BSPermissions.hasPermission(p, BetterShopPermission.USER_CHEST, true)) { + return; + } + openChest(p, (Chest) b.getState(), false); + } + } + } + + public void openChest(Player p, Chest open, boolean isEditing) { + if (p == null || open == null) { + return; + } + chestClose(p); + Location loc = open.getBlock().getLocation(); + //System.out.println("opening chest at " + loc.getBlockX() + ", " + loc.getBlockZ()); + Chest other = ChestManip.otherChest(open.getBlock()); + ItemStack[] chestShopItems = open.getInventory().getContents(); + IInventory chestShop; + String chestTxt = Str.strTrim(BetterShop.getSettings().chestShopText. + replace("", isEditing ? BetterShop.getSettings().chestEditText : ""), chestStrLen); + if (other != null) { + ItemStack[] chestShopItems2 = other.getInventory().getContents(); + if (isEditing) { + if (open == ChestManip.topChest(open)) { + chestShop = new InventoryLargeChest(chestTxt, + new InventorySmallChest("", chestShopItems), + new InventorySmallChest("", chestShopItems2)); + } else { + chestShop = new InventoryLargeChest(chestTxt, + new InventorySmallChest("", chestShopItems2), + new InventorySmallChest("", chestShopItems)); + } + } else { + ItemStack[] comb = open == ChestManip.topChest(open) + ? ArrayManip.arrayConcat(chestShopItems, chestShopItems2) + : ArrayManip.arrayConcat(chestShopItems2, chestShopItems); + ItemStack[] afford = canAfford(p, loc, comb); + + if(BetterShop.getSettings().chestSellBar){ + afford = ArrayManip.arrayConcat(afford, new ItemStack[9]); + } + + chestShop = new InventoryLargeChest(chestTxt, + new InventorySmallChest("", ArrayManip.arraySub(afford, 0, chestShopItems.length)), + new InventorySmallChest("", ArrayManip.arraySub(afford, chestShopItems.length, afford.length))); + + } + } else { + chestShop = new InventorySmallChest( + chestTxt, isEditing ? chestShopItems : canAfford(p, loc, chestShopItems)); + } + // Get the EntityPlayer handle from the sender + EntityPlayer entityplayer = ((CraftPlayer) p).getHandle(); + // open the "chest" + entityplayer.openContainer(chestShop); + + // save a copy of the chest's current inventory + openPlayers.put(p, ItemStackManip.copy(chestShop.getContents())); + openChests.put(p, chestShop); + openChestLocations.put(p, loc); + if (isEditing) { + editActive.add(p); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + chestClose(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerKick(PlayerKickEvent event) { + chestClose(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerMove(PlayerMoveEvent event) { + chestClose(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerDropItem(PlayerDropItemEvent event) { + if (openPlayers.containsKey(event.getPlayer())) { + event.setCancelled(true); + //event.getPlayer().updateInventory(); + } + } + + public synchronized void closeAllChests() { + for (Player p : openPlayers.keySet().toArray(new Player[0])) { + chestClose(p); + } + } + + void chestClose(Player player) { + if (!openPlayers.containsKey(player)) { + return; + } + ItemStack[] preItems = openPlayers.get(player), + newItems = ItemStackManip.copy(openChests.get(player).getContents());//player.getInventory().getContents(); + Location shopLocation = openChestLocations.get(player); + openPlayers.remove(player); + openChests.remove(player); + openChestLocations.remove(player); + if (editActive.contains(player)) { + editActive.remove(player); + chestEdit(shopLocation, newItems); + return; + } + try { + boolean diff = false; + for (int i = 0; i < preItems.length; ++i) { + if ((preItems[i] != null + && (!preItems[i].equals(newItems[i]) + || (newItems[i] != null && preItems[i].getAmount() != newItems[i].getAmount()))) + || (preItems[i] == null && newItems[i] != null)) { + diff = true; + break; + } + } + if (!diff) { + return; + } + // init shop + Shop shop = BetterShop.getShop(shopLocation); + // find the differences + List changedItems = ItemStackManip.itemStackDifferences(preItems, newItems); + // records + List transactions = new LinkedList(); + // name of item(s) sold + String itemN = ""; + int numItems = 0; + double total = 0; + // for transactions to cancel + List cancel = new LinkedList(); + // first sell + for (ItemStack i : changedItems) { + if (i.getAmount() > 0) { + //System.out.println("sold " + i.getAmount() + " " + i.getType().name()); + JItem toSell = JItemDB.GetItem(i); + double credit = shop.pricelist.itemSellPrice(player, toSell, i.getAmount()); + + if (credit < 0 || (toSell.IsTool() && !BetterShop.getSettings().buybacktools)) { + UserTransaction n = new UserTransaction(toSell, true, i.getAmount()); + if (toSell.IsTool()) { + n.damage = i.getDurability(); + } + cancel.add(n); + BSutils.sendMessage(player, BetterShop.getSettings().getString("donotwant"). + replace("", toSell.coloredName())); + continue; + } else if (toSell.IsTool()) { + credit *= (1 - ((double) i.getDurability() / toSell.MaxDamage())); + } + // now check the remaining stock can sell back + if (shop.config.useStock()) { + long avail = shop.stock.freeStockRemaining(toSell); + if (avail == 0 && shop.config.noOverStock) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("maxstock"). + replace("", toSell.coloredName())); + UserTransaction n = new UserTransaction(toSell, true, i.getAmount()); + if (toSell.IsTool()) { + n.damage = i.getDurability(); + } + cancel.add(n); + continue; + } else if (avail > 0 && i.getAmount() > avail && shop.config.noOverStock) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("highstock"). + replace("", toSell.coloredName()). + replace("", String.valueOf(avail))); + UserTransaction n = new UserTransaction(toSell, true, i.getAmount() - (int) avail); + if (toSell.IsTool()) { + n.damage = i.getDurability(); + } + cancel.add(n); + i.setAmount((int) avail); + } + } + + total += credit; + numItems += i.getAmount(); + itemN += toSell.coloredName() + ", "; + + transactions.add(new UserTransaction(toSell, true, + i.getAmount(), credit, player.getDisplayName())); + + if (shop.config.useStock()) { + shop.stock.changeItemAmount(toSell, i.getAmount()); + } + } + } + if (numItems > 0) { + BSEcon.addMoney(player, total); + BSutils.sendFormttedMessage(player, "sellmsg", + itemN.substring(0, itemN.length() - 2), numItems, total); + } + if (cancel.size() > 0) { + List reAdd = new ArrayList(); + for(UserTransaction u : cancel) { + reAdd.add(new ItemStack(u.itemNum, u.amount, u.damage)); + } + player.getInventory().setContents( + ItemStackManip.add(player.getInventory().getContents(), + reAdd, !BetterShop.getSettings().usemaxstack)); + delayUpdate(player); + } + + itemN = ""; + numItems = 0; + total = 0; + cancel.clear(); + double balance = BSEcon.getBalance(player); + // then buy + for (ItemStack i : changedItems) { + if (i.getAmount() < 0) { + //System.out.println("bought " + (-i.getAmount()) + " " + i.getType().name()); + JItem toBuy = JItemDB.GetItem(i); + double cost = shop.pricelist.itemBuyPrice(player, toBuy, -i.getAmount()); + if (balance - cost < 0) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("insuffunds"). + replace("", toBuy.coloredName()). + replace("", String.valueOf(-i.getAmount())). + replace("", String.valueOf(cost)). + replace("", BetterShop.getSettings().currency()). + replace("", String.valueOf(cost / (-i.getAmount()))). + replace("", BSEcon.format(cost))); + // change amount so can buy some + int afford = shop.pricelist.getAmountCanBuy(player, balance, toBuy); + if (afford > 0) { + cost = shop.pricelist.itemBuyPrice(player, toBuy, afford); + cancel.add(new UserTransaction(toBuy, false, -i.getAmount() - afford)); + i.setAmount(-(-i.getAmount() - afford)); + } else { + cancel.add(new UserTransaction(toBuy, false, -i.getAmount())); + continue; + } + } + + balance -= cost; + total += cost; + numItems += -i.getAmount(); + itemN += toBuy.coloredName() + ", "; + + transactions.add(new UserTransaction(toBuy, false, + -i.getAmount(), cost, player.getDisplayName())); + + if (shop.config.useStock()) { + shop.stock.changeItemAmount(toBuy, i.getAmount()); + } + } + } + // now check for & remove transactions that the user can't afford + if (cancel.size() > 0) { + List reAdd = new ArrayList(); + for(UserTransaction u : cancel) { + reAdd.add(new ItemStack(u.itemNum, u.amount, u.damage)); + } + player.getInventory().setContents( + ItemStackManip.add(player.getInventory().getContents(), + reAdd, !BetterShop.getSettings().usemaxstack)); + delayUpdate(player); + } + + if (numItems > 0) { + BSEcon.subtractMoney(player, total); + BSutils.sendFormttedMessage(player, "buymsg", + itemN.substring(0, itemN.length() - 2), numItems, total); + } + // apppend records + try { + for (UserTransaction t : transactions) { + shop.transactions.addRecord(t); + } + } catch (Exception ex) { + BetterShopLogger.Severe(ex); + } + } catch (Exception e) { + BetterShopLogger.Severe("Error in ChestShop", e); + } + } + + void chestEdit(Location l, ItemStack[] edited) { + if (l == null || edited == null) { + return; + } + Block b = l.getBlock(); + if (b.getState() instanceof Chest) { + //System.out.println("saving chest at " + l.getBlockX() + ", " + l.getBlockZ()); + //Chest open = (Chest) b.getState(); + ChestManip.setContents((Chest) b.getState(), edited); + } + } + + ItemStack[] canAfford(Player p, Location shopLoc, ItemStack[] source) { + ItemStack[] copy = new ItemStack[source.length]; + + // now scan through chest & increase item amounts to max player can afford + HashMap used = new HashMap(); + BSPriceList pricelist = BetterShop.getPricelist(shopLoc); + for (int i = 0; i < source.length; ++i) { + if (source[i] == null) { + copy[i] = null; + } else { + JItem it = JItemDB.GetItem(source[i]); + if (it != null) { + int max = pricelist.getAmountCanBuy(p, it); + int maxSize = BetterShop.getSettings().usemaxstack ? it.getMaxStackSize() : 64; + if (max < 0) { + max = maxSize; // Integer.MAX_VALUE; + } else if (max > 0) { + if (used.containsKey(it.IdDatStr())) { + int u = used.get(it.IdDatStr()); + max -= u; + used.put(it.IdDatStr(), u + maxSize); + } else { + used.put(it.IdDatStr(), maxSize); + } + } + if (max > maxSize) { + max = maxSize; + } + if (max <= 0) { + copy[i] = null; + } else { + copy[i] = source[i].clone(); + copy[i].setAmount(max); + } + } + } + } + return copy; + } + + public boolean defineChestShop(Block b) { + if (b == null || chestsDB.has(b.getLocation())) { + return false; + } else { + chestsDB.setChest(b.getLocation()); + return true; + } + } + + public boolean removeChestShop(Block b) { + if (b == null || !chestsDB.has(b.getLocation())) { + return false; + } else { + chestsDB.remove(b.getLocation(), true); + return true; + } + } + + public boolean hasChestShop(Block b) { + return b != null && chestsDB.has(b.getLocation()); + } + + public boolean load() { + return chestsDB.load(); + } + + public boolean save() { + return !chestsDB.isChanged() || chestsDB.save(); + } + + public boolean saveDelayActive() { + return chestsDB.saveDelayActive(); + } + + public int numChests() { + return chestsDB.getSavedChests().size(); + } + HashMap playerUpdate = new HashMap(); + + private void delayUpdate(Player p) { + BukkitScheduler sc = plugin.getServer().getScheduler(); + if (playerUpdate.containsKey(p)) { + sc.cancelTask(playerUpdate.get(p)); + playerUpdate.remove(p); + } + int t = sc.scheduleSyncDelayedTask(plugin, new UpdateTask(p), 1); + if (t != -1) { + playerUpdate.put(p, t); + } + } + + private class UpdateTask implements Runnable { + + Player player; + + UpdateTask(Player p) { + player = p; + } + + public void run() { + playerUpdate.remove(player); + player.updateInventory(); + } + } +} // end class BSSignShop + diff --git a/src/me/jascotty2/bettershop/chestshop/ChestDB.java b/src/me/jascotty2/bettershop/chestshop/ChestDB.java new file mode 100644 index 0000000..d1911af --- /dev/null +++ b/src/me/jascotty2/bettershop/chestshop/ChestDB.java @@ -0,0 +1,187 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: chest shop database + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.chestshop; + +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.utils.BetterShopLogger; + +import me.jascotty2.lib.io.FileIO; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.bukkit.inventory.ChestManip; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; + +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.block.Chest; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class ChestDB { + + public final static long signDBsaveWait = 30000; // don't save immediately, wait (30s) + List chests = new ArrayList(); + Map savedChests = new HashMap(); + boolean changed = false; + private DelayedSaver delaySaver = null; + final Server server; + + public ChestDB(Server sv) { + if (sv == null) { + throw new IllegalArgumentException("Plugin Cannot be Null"); + } + server = sv; + } + + public synchronized boolean load() { + if (BSConfig.chestDBFile.exists()) { + try { + List signdb = FileIO.loadCSVFile(BSConfig.chestDBFile); + for (String[] s : signdb) { + if (s.length >= 4 && server.getWorld(s[0]) != null) { + chests.add(new Location(server.getWorld(s[0]), + CheckInput.GetDouble(s[1], 0), + CheckInput.GetDouble(s[2], 0), + CheckInput.GetDouble(s[3], 0))); + } + } + // now scan & double-check these are all chests + for (Location l : chests) { + if (!(l.getBlock().getState() instanceof Chest)) { + chests.remove(l); + } else { + //savedChests.put(l, ChestManip.getContents((Chest) l.getBlock().getState())); + savedChests.put(l, ((Chest) l.getBlock().getState()).getInventory().getContents()); + } + } + return true; + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + return false; + } + return true; + } + + public synchronized boolean save() { + try { + if (delaySaver != null) { + delaySaver.cancel(); + delaySaver = null; + } + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, e); + } + try { + ArrayList file = new ArrayList(); + for (Location l : chests) { + file.add(l.getWorld().getName() + "," + + l.getBlockX() + "," + l.getBlockY() + "," + l.getBlockZ()); + } + return FileIO.saveFile(BSConfig.chestDBFile, file) && !(changed = false); + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, e); + } + return false; + } + + public boolean has(Location l){ + return chests.contains(l); + } + + public void setChest(Location l) { + if (l != null && !chests.contains(l)) { + chests.add(l.clone()); + //savedChests.put(l.clone(), ChestManip.getContents((Chest) l.getBlock().getState())); + savedChests.put(l, ((Chest) l.getBlock().getState()).getInventory().getContents()); + Chest other = ChestManip.otherChest(l.getBlock()); + if(other != null){ + chests.add(other.getBlock().getLocation()); + savedChests.put(other.getBlock().getLocation(), other.getInventory().getContents()); + } + changed = true; + delaySave(); + } + } + + public void remove(Location l) { + chests.remove(l); + savedChests.remove(l); +// if(l.getBlock().getState() instanceof Chest){ +// ((Chest)l.getBlock().getState()).getInventory().clear(); +// } + changed = true; + delaySave(); + } + + public void remove(Location l, boolean removeDouble) { + remove(l); + if(removeDouble){ + Chest other = ChestManip.otherChest(l.getBlock()); + if(other != null){ + remove(other.getBlock().getLocation()); + } + } + } + + public boolean savedChestExists(Location l) { + return chests.contains(l); + } + + public Set> getSavedChests() { + return savedChests.entrySet(); + } + + public void delaySave() { + if (delaySaver != null) { + delaySaver.cancel(); + } + delaySaver = new DelayedSaver(); + delaySaver.start(signDBsaveWait); + } + + public boolean saveDelayActive(){ + return delaySaver != null; + } + + protected class DelayedSaver extends TimerTask { + public void start(long wait) { + (new Timer()).schedule(this, wait); + } + + @Override + public void run() { + save(); + } + } + + public boolean isChanged() { + return changed; + } + +} // end class ChestDB + diff --git a/src/me/jascotty2/bettershop/chestshop/ChestListener.java b/src/me/jascotty2/bettershop/chestshop/ChestListener.java new file mode 100644 index 0000000..c1b11b6 --- /dev/null +++ b/src/me/jascotty2/bettershop/chestshop/ChestListener.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for checking & maintaining chests & items + * (can be used to prevent accidental destruction / deletion of shop chests) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.chestshop; + +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.lib.bukkit.inventory.ChestManip; +import org.bukkit.Server; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; + +// end class SignRestore +/** + * @author jacob + */ +public class ChestListener implements Listener /*implements Runnable*/ { + + final Plugin plugin; + final Server server; + final ChestDB chestsBD; + final DamageBlocker blockBreakBlock; + + public ChestListener(Plugin p, ChestDB chestsBD) { + if (p == null || chestsBD == null) { + throw new IllegalArgumentException("Arguments Cannot be Null"); + } + plugin = p; + server = p.getServer(); + this.chestsBD = chestsBD; + blockBreakBlock = new DamageBlocker(p, chestsBD); + } + + public void startProtect() { + plugin.getServer().getPluginManager().registerEvents(blockBreakBlock, plugin); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockPlace(BlockPlaceEvent event) { + if (!event.isCancelled()) { + if (chestsBD.savedChestExists(event.getBlock().getLocation())) { + chestsBD.remove(event.getBlock().getLocation()); + } else if (event.getBlockPlaced().getState() instanceof Chest) { + Chest other = ChestManip.otherChest(event.getBlock()); + if (other != null && chestsBD.savedChestExists(other.getBlock().getLocation())) { + chestsBD.setChest(event.getBlock().getLocation()); + BSutils.sendMessage(event.getPlayer(), "Chest Shop Expanded"); + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent event) { + if (!event.isCancelled() && chestsBD.savedChestExists(event.getBlock().getLocation())) { + if (BetterShop.getSettings().chestDestroyProtection + && !BSPermissions.hasPermission(event.getPlayer(), BetterShopPermission.ADMIN_CHESTS, true)) { + event.setCancelled(true); + } else { + chestsBD.remove(event.getBlock().getLocation()); + BSutils.sendMessage(event.getPlayer(), "Chest Shop Removed"); + } + } + } +} + +class DamageBlocker implements Listener { + + final Plugin plugin; + final Server server; + final ChestDB chestsBD; + + DamageBlocker(Plugin p, ChestDB chestsBD) { + plugin = p; + server = p.getServer(); + this.chestsBD = chestsBD; + } + + @EventHandler(priority = EventPriority.LOW) + public void onEntityExplode(EntityExplodeEvent event) { + if (!event.isCancelled() && BetterShop.getSettings().chestTNTprotection) { + for (Block b : event.blockList()) { + if (chestsBD.has(b.getLocation())) { + event.setCancelled(true); + return; + } + } + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onEntityBlockInteract(EntityInteractEvent event) { + // ought to work for enderman pickup and other break events, but not sure how to test.. + if (!event.isCancelled() && event.getBlock() != null + && !(event.getEntity() instanceof Player) + && BetterShop.getSettings().signDestroyProtection) { + if (chestsBD.has(event.getBlock().getLocation())) { + event.setCancelled(true); + return; + } + } + } +} diff --git a/src/me/jascotty2/bettershop/chestshop/ChestShopInventoryListenerSpout.java b/src/me/jascotty2/bettershop/chestshop/ChestShopInventoryListenerSpout.java new file mode 100644 index 0000000..ea8bc97 --- /dev/null +++ b/src/me/jascotty2/bettershop/chestshop/ChestShopInventoryListenerSpout.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( TODO ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.chestshop; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; + +public class ChestShopInventoryListenerSpout implements Listener { + + final BSChestShop callback; + + public ChestShopInventoryListenerSpout(BSChestShop callback) { + this.callback = callback; + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if ((event.getPlayer() != null)) { + callback.chestClose((Player) event.getPlayer()); + } + } +} // end class ChestShopPlayerListener + diff --git a/src/me/jascotty2/bettershop/chestshop/InventorySmallChest.java b/src/me/jascotty2/bettershop/chestshop/InventorySmallChest.java new file mode 100644 index 0000000..615b0ec --- /dev/null +++ b/src/me/jascotty2/bettershop/chestshop/InventorySmallChest.java @@ -0,0 +1,154 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( TODO ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.chestshop; + +import java.util.List; +import net.minecraft.server.v1_7_R1.EntityHuman; +import net.minecraft.server.v1_7_R1.IInventory; +import net.minecraft.server.v1_7_R1.Item; +import net.minecraft.server.v1_7_R1.ItemStack; +import org.bukkit.craftbukkit.v1_7_R1.entity.CraftHumanEntity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.InventoryHolder; + +public class InventorySmallChest implements IInventory { + ItemStack items[];String name; + public InventorySmallChest(String name, org.bukkit.inventory.ItemStack itms[]) { + this.name=name; + items = new ItemStack[itms.length]; + for(int i=0; i= items.length ? null : items[i]; + } + + @Override + public ItemStack splitStack(int i, int j) { + if (i >= this.items.length) { + return null; + } + ItemStack[] aitemstack = this.items; + + if (aitemstack[i] != null) { + ItemStack itemstack; + + if (aitemstack[i].count <= j) { + itemstack = aitemstack[i]; + aitemstack[i] = null; + return itemstack; + } else { + itemstack = aitemstack[i].a(j); + if (aitemstack[i].count == 0) { + aitemstack[i] = null; + } + + return itemstack; + } + } else { + return null; + } + } + + @Override + public void setItem(int i, ItemStack is) { + items[i] = is; + } + + @Override + public int getMaxStackSize() { + return 64; + } + + @Override + public void update() { + } + + @Override + public ItemStack[] getContents() { + return items; + } + + @Override + public boolean a(EntityHuman eh) { + return true; + } + + @Override + public ItemStack splitWithoutUpdate(int i) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void onOpen(CraftHumanEntity che) { + } + + @Override + public void onClose(CraftHumanEntity che) { + } + + @Override + public List getViewers() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public InventoryHolder getOwner() { + return null; + } + + @Override + public void setMaxStackSize(int i) { + } + + @Override + public void startOpen() { + } + + @Override + public boolean b(int i, ItemStack is) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public String getInventoryName() { + return name; + } + + @Override + public boolean k_() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void l_() { + throw new UnsupportedOperationException("Not supported."); + } + +} // end class InventorySmallChest + diff --git a/src/me/jascotty2/bettershop/commands/AdminCommands.java b/src/me/jascotty2/bettershop/commands/AdminCommands.java new file mode 100644 index 0000000..99f99f0 --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/AdminCommands.java @@ -0,0 +1,400 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods for plugin administration + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop.ServerReload; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.NestedCommand; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.PriceListItem; +import me.jascotty2.lib.bukkit.shop.PriceList; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.util.Str; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class AdminCommands { + + final static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy_MM_dd_HH-mm-ss"); + + @Command(commands = {"shopload"}, + aliases = {"load", "reload"}, + desc = "Reload Shop & Settings", + permissions = {"BetterShop.admin.load"}) + public static boolean load(CommandSender player, String[] s) { + if (player != null && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_LOAD, true)) { + return true; + } + int err = BetterShop.reload(player); + BSutils.sendMessage(player, "Reload Complete with " + (err == 0 ? "no" : String.valueOf(err)) + " Errors"); + return player != null || err == 0; + } + + @Command(commands = {"shopadd", "sadd"}, + aliases = {"add", "ad"}, + desc = "Add an item to, or update an item in, the price list", + usage = "[item] [buy-price] [sell-price]", + min = 2, + max = 3, + permissions = {"BetterShop.admin.add"}) + public static boolean add(CommandSender player, String[] s) { + if (s.length == 2) { + // append -1 as sell price + String ns[] = new String[3]; + for (int i = 0; i < 2; ++i) { + ns[i] = s[i]; + } + ns[2] = "-1"; + s = ns; + } + if (s.length != 3) { + return false; + } + if (!BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ADD, true)) { + return true; + } + + JItem toAdd = JItemDB.findItem(s[0]); + if (toAdd == null) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("unkitem").replace("", s[0])); + return false; + } + + if (CheckInput.IsDouble(s[1]) && CheckInput.IsDouble(s[2])) { + double buy = CheckInput.GetDouble(s[1], -1), + sel = CheckInput.GetDouble(s[2], -1); + if (buy > PriceList.MAX_PRICE + || sel > PriceList.MAX_PRICE) { + BSutils.sendMessage(player, "Price set too high. Max = " + BSEcon.format(PriceList.MAX_PRICE)); + return true; + } else if (toAdd.isKit() && sel >= 0) { + BSutils.sendMessage(player, "Note: Kits cannot be sold"); + s[2] = "-1"; + } else if (toAdd.isEntity() && sel >= 0) { + BSutils.sendMessage(player, "Note: Entities cannot be sold"); + s[2] = "-1"; + } + try { + Shop shop = BetterShop.getShop( + player instanceof Player ? ((Player) player).getLocation() : null); + boolean isChanged = shop.pricelist.itemExists(toAdd); + if (shop.pricelist.setPrice(toAdd, buy, sel)) { + PriceListItem nPrice = shop.pricelist.getItemPrice(toAdd); + double by = nPrice == null ? -2 : nPrice.buy, + sl = nPrice == null ? -2 : nPrice.sell; + + BSutils.sendMessage(player, BetterShop.getSettings().getString(isChanged ? "chgmsg" : "addmsg"). + replace("", toAdd.coloredName()). + replace("", String.format("%.2f", by)). + replace("", String.format("%.2f", sl)). + replace("", BetterShop.getSettings().currency()). + replace("", BSEcon.format(by)). + replace("", BSEcon.format(sl)), + BetterShop.getSettings().publicmarket); + + if (!isChanged && shop.config.useStock()) { + shop.stock.setItemAmount(toAdd, shop.config.startStock); + } + return true; + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + BSutils.sendMessage(player, ChatColor.RED + "An Error Occurred While Adding."); + } else { + BSutils.sendMessage(player, BetterShop.getSettings().getString("paramerror")); + return false; + } + + return true; + } + + @Command(commands = {"shopremove", "sremove"}, + aliases = {"remove", "rm"}, + desc = "Remove an item from the price list", + usage = "[item]", + min = 1, + max = 1, + permissions = {"BetterShop.admin.remove"}) + public static boolean remove(CommandSender player, String[] s) { + if ((!BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_REMOVE, true))) { + return true; + } else if (s == null || s.length != 1) { + return false; + } + JItem toRem = JItemDB.findItem(s[0]); + if (toRem != null) { + try { + Shop shop = BetterShop.getShop( + player instanceof Player ? ((Player) player).getLocation() : null); + + shop.pricelist.remove(toRem); + shop.stock.remove(toRem); + + BSutils.sendMessage(player, BetterShop.getSettings().getString("removemsg"). + replace("", toRem.coloredName()), BetterShop.getSettings().publicmarket); + + return true; + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + BSutils.sendMessage(player, ChatColor.RED + "Error removing item"); + } else { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("unkitem").replace("", s[0])); + } + return true; + + } + + @Command(commands = {}, + aliases = {"restock"}, + desc = "Restock the Shop's Stock of Items", + usage = "[item [item ...]]", + permissions = {"BetterShop.admin.restock"}) + public static void restock(CommandSender sender, String[] s) { + //TODO: + // if item[s] specified, restock only those + BetterShop.restock(); + BSutils.sendMessage(sender, "Stock set to initial values"); + } + + @Command(commands = {}, + aliases = {"backup"}, + desc = "Backup the pricelist to a csv file", + usage = "[filename]", + permissions = {"BetterShop.admin.backup"}) + public static void backupDB(CommandSender sender, String[] s) { + String fn; + + if (s.length >= 1) { + fn = Str.concatStr(s, 1, " "); + if (fn.toLowerCase().endsWith(".csv")) { + fn = fn.substring(0, fn.length() - 5); + } + } else { + fn = dateFormatter.format(new java.util.Date()); + } + + if (s.length >= 1) { + Shop sh = BetterShop.getShop(s[0]); + if (sh != null) { + if (backupDB(sh, fn)) { + BSutils.sendMessage(sender, ChatColor.GREEN + "Backup saved as " + fn); + } else { + BSutils.sendMessage(sender, ChatColor.RED + "Failed to save backup file " + fn); + } + return; + } + } + + for (Shop shop : BetterShop.getShops()) { + String filename; + if (s.length > 1) { + filename = fn + "_" + shop.config.tableName;//shop.getName(); + } else { + filename = shop.config.tableName + "_" + fn; + } + if (backupDB(shop, filename)) { + BSutils.sendMessage(sender, ChatColor.GREEN + "Backup saved as " + filename); + } else { + BSutils.sendMessage(sender, ChatColor.RED + "Failed to save backup file " + filename); + } + } + + } + + protected static boolean backupDB(Shop shop, String saveAs) { + if (!saveAs.toLowerCase().endsWith(".csv")) { + saveAs += ".csv"; + } + try { + return shop.pricelist.saveFile(new File(BSConfig.pluginFolder.getPath() + + File.separatorChar + saveAs)); + } catch (IOException ex) { + BetterShopLogger.Severe("Failed to save backup file " + saveAs, ex); + } + return false; + } + + @Command(commands = {}, + aliases = {"import"}, + desc = "import items to pricelist from a csv file", + usage = "[filename]", + permissions = {"BetterShop.admin.backup"}) + public static boolean importDB(CommandSender player, String[] s) { + String fname = Str.concatStr(s, " "); + if (fname.length() == 0) { + BSutils.sendMessage(player, "Need a file to import"); + return true; + } + + File toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + if (!toImport.exists() && !fname.toLowerCase().endsWith(".csv")) { + fname += ".csv"; + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + } + if (!toImport.exists() && s.length > 1) { + // check if the first string is a shop name + Shop shop = BetterShop.getShop(s[0]); + if (shop != null) { + fname = Str.concatStr(s, 1, " "); + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + if (!toImport.exists() && !fname.toLowerCase().endsWith(".csv")) { + fname += ".csv"; + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + } + if (toImport.exists()) { + if (shop.pricelist.importDB(toImport)) { + BSutils.sendMessage(player, "Database Imported"); + } else { + BSutils.sendMessage(player, ChatColor.RED + " An Error Occured while importing database"); + } + } + } + } + if (!toImport.exists()) { + BSutils.sendMessage(player, fname + " does not exist"); + return true; + } + if (BetterShop.getMainPricelist().importDB(toImport)) { + BSutils.sendMessage(player, "Database Imported"); + } else { + BSutils.sendMessage(player, ChatColor.RED + " An Error Occured while importing database"); + } + return true; + } + + @Command(commands = {}, + aliases = {"restore"}, + desc = "restore a pricelist from a csv file", + usage = "[filename]", + permissions = {"BetterShop.admin.backup"}) + public static boolean restoreDB(CommandSender player, String[] s) { + String fname = Str.concatStr(s, " "); + if (fname.length() == 0) { + BSutils.sendMessage(player, "Need a file to import"); + return true; + } + + File toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + if (!toImport.exists() && !fname.toLowerCase().endsWith(".csv")) { + fname += ".csv"; + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + } + if (!toImport.exists() && s.length > 1) { + // check if the first string is a shop name + Shop shop = BetterShop.getShop(s[0]); + if (shop != null) { + fname = Str.concatStr(s, 1, " "); + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + if (!toImport.exists() && !fname.toLowerCase().endsWith(".csv")) { + fname += ".csv"; + toImport = new File(BSConfig.pluginFolder.getPath() + File.separatorChar + fname); + } + if (toImport.exists()) { + if (shop.pricelist.importDB(toImport)) { + BSutils.sendMessage(player, "Database Imported"); + } else { + BSutils.sendMessage(player, ChatColor.RED + " An Error Occured while importing database"); + } + } + } + } + if (!toImport.exists()) { + BSutils.sendMessage(player, fname + " does not exist"); + return true; + } + if (BetterShop.getMainPricelist().restoreDB(toImport)) { + BSutils.sendMessage(player, "Database Imported"); + } else { + BSutils.sendMessage(player, ChatColor.RED + " An Error Occured while importing database"); + } + return true; + } + + @Command(commands = {}, + aliases = {"version", "v", "ver"}, + desc = "Check the current version", + permissions = {"BetterShop.admin.info", "jascotty2", "jjfs85"}) + public static void version(CommandSender sender, String[] s) { + // allow admin.info or developers access to plugin status + // (so if i find a bug i can see if it's current) + BSutils.sendMessage(sender, "version " + BetterShop.getPlugin().getDescription().getVersion()); + } + + @Command(commands = {}, + aliases = {"inventory", "inv"}, + desc = "Print Inventory to the screen (debugging)", + permissions = {"BetterShop.admin.inv", "jascotty2", "jjfs85"}) + public static void inv(CommandSender sender, String[] s) { + if(!(sender instanceof Player)) { + sender.sendMessage("Must be a player"); + } else { + ItemStack[] inv = ((Player)sender).getInventory().getContents(); + for(int i = 0; i < inv.length; ++i) { + sender.sendMessage(String.format("%2d: %s", i, inv[i] == null ? "null" : inv[i].getTypeId() + ":" + inv[i].getData().getData() + " " + inv[i].getItemMeta().getDisplayName() + " (" + inv[i].getAmount() + ") " + JItemDB.GetItemName(inv[i]))); + BetterShop.getPlugin().getLogger().info(String.format("%2d: %s", i, inv[i] == null ? "null" : inv[i].getTypeId() + ":" + inv[i].getData().getData() + " " + inv[i].getItemMeta()+ " (" + inv[i].getAmount() + ") " + JItemDB.GetItemName(inv[i]))); + } + } + } + + @Command(commands = {}, + aliases = {"define", "d", "def"}, + desc = "generic define command") + @NestedCommand({Commands.class}) + public static void define(CommandSender sender, String[] s) { + } + + public static class Commands { + + @Command(commands = {}, + aliases = {"chest", "c", "ch"}, + desc = "define a new chest shop") + public static void chest(CommandSender sender, String[] s) { + ChestCommands.Commands.define(sender, s); + } + + @Command(commands = {}, + aliases = {"region", "r", "reg"}, + desc = "define a new shop region") + public static void region(CommandSender sender, String[] s) { + RegionCommands.Commands.define(sender, s); + } + } +} // end class AdminCommands + diff --git a/src/me/jascotty2/bettershop/commands/BSCommandManager.java b/src/me/jascotty2/bettershop/commands/BSCommandManager.java new file mode 100644 index 0000000..c5fc716 --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/BSCommandManager.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: handles commands for bettershop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.commands; + +import java.util.logging.Logger; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.commands.CommandManager; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * @author jacob + */ +public class BSCommandManager extends CommandManager { + + //final Class[] functionParameter = new Class[]{CommandSender.class, String[].class}; + final Class[] commandClasses = new Class[]{ + ShopCommand.class, AdminCommands.class, + BuyCommands.class, SellCommands.class, + ListCommands.class, HelpCommands.class}; + + public BSCommandManager() { + for(Class c : commandClasses){ + registerCommandClass(c); + } + } + + @Override + protected Logger getLogger() { + return BetterShopLogger.getLogger(); + } + + @Override + public boolean hasPermission(CommandSender player, String perm) { + if(perm.toUpperCase().equals("OP")){ + return player.isOp(); + } else if(!perm.contains(".") + && player instanceof Player + && perm.equalsIgnoreCase(((Player)player).getName())){ + return true; + } + return BSPermissions.hasPermission(player, perm, false); + } +} // end class BSCommandManager + diff --git a/src/me/jascotty2/bettershop/commands/BuyCommands.java b/src/me/jascotty2/bettershop/commands/BuyCommands.java new file mode 100644 index 0000000..f99b49c --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/BuyCommands.java @@ -0,0 +1,741 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods for buying + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.bettershop.shop.Shop; + +import me.jascotty2.lib.bukkit.item.CreatureItem; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.JItems; +import me.jascotty2.lib.bukkit.shop.UserTransaction; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.util.Str; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.WrappedCommandException; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.lib.bukkit.item.Kit; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +/** + * @author jacob + */ +public class BuyCommands { + + static Map userbuyHistory = new HashMap(); + + @Command(commands = {"shopbuy", "sbuy", "buy"}, + aliases = {"buy", "b", "sb"}, + desc = "Buy an item from the shop", + usage = " [amount]", + min = 1, + max = 2, + permissions = {"BetterShop.user.buy"}) + public static void buy(CommandSender player, String[] s) throws WrappedCommandException { + if (BSutils.anonymousCheck(player)) { + return; + } + if (s.length == 2 && (s[0].equalsIgnoreCase("all") || s[0].equalsIgnoreCase("a") + || (CheckInput.IsInt(s[0]) && !CheckInput.IsInt(s[1]) + && !(s[1].equalsIgnoreCase("all") || s[1].equalsIgnoreCase("a"))))) { + // swap two indicies + String t = s[0]; + s[0] = s[1]; + s[1] = t; + } + + int amt = 1; + if (s.length == 2) { + if (CheckInput.IsInt(s[1])) { + if ((amt = CheckInput.GetInt(s[1], -1)) <= 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("nicetry")); + return; + } + } else if (s[1].equalsIgnoreCase("all") || s[1].equalsIgnoreCase("a")) { + amt = -1; + } else { + BSutils.sendMessage(player, ChatColor.RED + s[1] + " Is Not a Valid Number"); + return; + } + } + + JItem toBuy = JItemDB.isCategory(s[0]) ? null : JItemDB.findItem(s[0]), buyCat[] = null; + if (toBuy == null) { + buyCat = JItemDB.getItemsByCategory(s[0]); + if (buyCat == null || buyCat.length == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem").replace("", s[0])); + return; + } + } else if (toBuy.ID() <= 0) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("notforsale"). + replace("", toBuy.coloredName())); + return; + } else if (!BetterShop.getSettings().allowbuyillegal && !toBuy.IsLegal() && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false)) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("illegalbuy"). + replace("", toBuy.coloredName())); + return; + } + // initial check complete: set as last action + userbuyHistory.put(((Player) player).getDisplayName(), "shopbuy " + Str.concatStr(s, " ")); + + try { + if (toBuy != null) { + buyItem((Player) player, toBuy, amt, -1); + } else { + // buying by category + buyItem((Player) player, buyCat, amt, -1); + } + } catch (Exception ex) { + throw new WrappedCommandException(ex); + } + } + + @Command(commands = {"shopbuyall", "sbuyall", "buyall"}, + aliases = {"buyall", "ba", "ball"}, + desc = "Buy all that you can of an item from the shop", + usage = " [item ... ]", + min = 1, + permissions = {"BetterShop.user.buy"}) + public static void buyall(CommandSender player, String[] s) throws WrappedCommandException { + for (int i = 0; i < s.length; ++i) { + buy(player, new String[]{s[i], "all"}); + } + } + + @Command(commands = {"shopbuystack", "sbuystack", "sbuys", "buys"}, + aliases = {"buystack", "bs"}, + desc = "Buy an item from the shop", + usage = " [amount]", + min = 1, + permissions = {"BetterShop.user.buy"}) + public static boolean buystack(CommandSender player, String[] s) throws WrappedCommandException { + if (BSutils.anonymousCheck(player)) { + return true; + } + try { + int amt = 1, l = s.length - 1; + if (s.length >= 2 && CheckInput.IsInt(s[l])) { + amt = CheckInput.GetInt(s[l], 1); + --l; + } + if (s[0].contains(",")) { + String[] its = s[0].split(","); + String[] newArgs = new String[s.length - 1 + its.length]; + System.arraycopy(its, 0, newArgs, 0, its.length); + System.arraycopy(s, 1, newArgs, its.length - 1, s.length - 1); + s = newArgs; + l += its.length - 1; + } + ArrayList toBuy = new ArrayList(); + //Kit toBuy = new Kit(); + boolean mx = BetterShop.getSettings().usemaxstack; + for (int i = 0; i <= l; ++i) { + JItem j = JItemDB.isCategory(s[i]) ? null : JItemDB.findItem(s[i]), buyCat[] = null; + if (j == null) { + buyCat = JItemDB.getItemsByCategory(s[i]); + if (buyCat == null || buyCat.length == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem").replace("", s[i])); + continue; + } else if (j.ID() <= 0) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("notforsale"). + replace("", j.coloredName())); + continue; + } else if (!BetterShop.getSettings().allowbuyillegal && !j.IsLegal() && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false)) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("illegalbuy"). + replace("", j.coloredName())); + continue; + } + } + if (j != null) { + toBuy.add(new JItem[]{j}); + //toBuy.AddItem(j, mx ? j.MaxStackSize() : 64); + } else { + toBuy.add(buyCat); +// for(JItem c : buyCat){ +// toBuy.AddItem(c, mx ? c.MaxStackSize() : 64); +// } + } + } + if (toBuy.size() > 0) { //.numItems() > 0){ + //buyItem((Player) player, toBuy, 1, -1); + for (JItem[] it : toBuy) { + buyItem((Player) player, it, amt * (mx && it.length == 1 ? it[0].MaxStackSize() : 64), -1); + } + } + } catch (Exception ex) { + throw new WrappedCommandException(ex); + } + // overwrite history that buy wrote + userbuyHistory.put(((Player) player).getDisplayName(), "shopbuystack " + Str.concatStr(s, " ")); + return true; + } + + @Command(commands = {"shopbuyagain", "sbuyagain", "buyagain", "sba"}, + aliases = {"buyagain", "b!", "buy!"}, + desc = "Repeat the last purchase action the player did", + usage = "", + permissions = {"BetterShop.user.buy"}) + public static void buyagain(CommandSender sender, String[] s) { + if (BSutils.anonymousCheck(sender)) { + return; + } + String action = userbuyHistory.get(((Player) sender).getDisplayName()); + if (action == null) { + BSutils.sendMessage(sender, + "You have no recent sell history"); + return; + } +// else { +// // trim command & put into args +// String cm[] = action.split(" "); +// String commandName = cm[0]; +// s = new String[cm.length - 1]; +// for (int i = 1; i < cm.length; ++i) { +// s[i - 1] = cm[i]; +// } +// } + ((Player) sender).performCommand(action); + } + + public static List getCanBuy(Player player, JItem[] toBuy) throws SQLException, Exception { + return getCanBuy(player, toBuy, -1); + } + + public static List getCanBuy(Player player, JItem toBuy[], double customPrice) throws SQLException, Exception { + ArrayList purchase = new ArrayList(); + Shop shop = BetterShop.getShop(player); + int newSize = 0; + long maxAvail[] = new long[toBuy.length]; + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null && !shop.pricelist.canBuy(toBuy[i])) { + toBuy[i] = null; + } else if (toBuy[i] != null) { + maxAvail[i] = shop.config.useStock() ? shop.stock.freeStockRemaining(toBuy[i]) : -1; +// System.out.println("can buy " + maxAvail[i] + " of " + toBuy[i].Name()); + ++newSize; + } + } + + ItemStack start[] = ItemStackManip.copy(player.getInventory().getContents()), + result[] = ItemStackManip.copy(start); +// for(int i = 0; i diff = ItemStackManip.itemStackDifferences(start, result); + +// System.out.println("after adding: " + diff.size() + " new items"); +// for(ItemStack i : diff) { +// System.out.println(JItemDB.GetItemName(i) + ":" + i.getDurability() + "x" + i.getAmount()); +// } + + if (diff.isEmpty()/* || diff.size() > newSize*/) { + return purchase; + } + int minbuy = diff.get(0).getAmount(), maxbuy = 0; + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null) { + int in = ItemStackManip.indexOf(diff, toBuy[i]); + if (in < 0) { + --newSize; + toBuy[i] = null; + } else { + int a = diff.get(in).getAmount(); + if (maxAvail[i] >= 0 && a > maxAvail[i]) { + a = (int) maxAvail[i]; + } + if (maxbuy < a) { + maxbuy = diff.get(in).getAmount(); + } + if (minbuy > diff.get(in).getAmount()) { + minbuy = diff.get(in).getAmount(); + } + } + } + } + + double cash = BSEcon.getBalance(player); + double baseprice = 0; + + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null) { + baseprice += customPrice >= 0 ? customPrice : shop.pricelist.getBuyPrice(toBuy[i]); +// System.out.println(toBuy[i].Name() + ": " + shop.pricelist.getBuyPrice(toBuy[i])); + } + } +// System.out.println("baseprice: " + baseprice + "/" + cash); + if (baseprice > cash) { + return purchase; + } + baseprice /= newSize; + + //start at estimated max + int amt = (int) (cash / baseprice); + if (amt > minbuy) { + amt = minbuy; + } +// System.out.println("trying " + amt + " each"); + if (customPrice < 0) { + // now loop until can't afford + /** (this is in anticipation of scaled prices) **/ + // first check if base amt is too much + double price; + do { + price = 0; + + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null) { + price += shop.pricelist.itemBuyPrice(player, toBuy[i], + amt > maxAvail[i] && maxAvail[i] > 0 ? (int) maxAvail[i] : amt); + } + } + } while (price > cash && --amt > 0); + + while (price + baseprice < cash) { +// System.out.println("trying " + amt + " each"); + ++amt; + + price = 0; + + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null) { + price += shop.pricelist.itemBuyPrice(player, toBuy[i], + amt > maxAvail[i] && maxAvail[i] > 0 ? (int) maxAvail[i] : amt); + } + } + if (price > cash) { + --amt; + break; + } else if (amt >= maxbuy) { // check if at max can buy + amt = maxbuy; + break; + } + } +// System.out.println("final: " + amt + " each: " + price); + } + +// System.out.println("after removing can't afford:"); + if (amt > 0) { + for (int i = 0; i < toBuy.length; ++i) { + if (toBuy[i] != null && maxAvail[i] != 0) { + purchase.add(new ItemStockEntry(toBuy[i], + amt > maxAvail[i] && maxAvail[i] > 0 ? (int) maxAvail[i] : amt)); +// System.out.println(toBuy[i].Name() + " x " + purchase.get(purchase.size()-1).amount + " (of " + maxAvail[i] + ")"); + } + } + } + + return purchase; + } + +// public static void buyAllItem(Player player, JItem toBuy) { +// if (player == null || toBuy == null) { +// return; +// } +// _buyItem(player, new JItem[]{toBuy}, BSutils.amtCanHold(player, toBuy), -1); +// } +// +// public static void buyAllItem(Player player, JItem toBuy, double customPrice){ +// if (player == null || toBuy == null) { +// return; +// } +// _buyItem(player, new JItem[]{toBuy}, BSutils.amtCanHold(player, toBuy), customPrice); +// } +// +// public static void buyAllItem(Player player, JItem[] toBuy, double customPrice) throws SQLException, Exception { +// if (player == null || toBuy == null || toBuy.length == 0) { +// return; +// } +// int amt; +// if(toBuy.length == 1){ +// amt = BSutils.amtCanHold(player, toBuy[0]); +// } else { +// List open = getCanBuy(player, toBuy, -1); +// amt = 0; +// for(ItemStockEntry i : open){ +// if(i.amount > amt){ +// amt = (int) i.amount; +// } +// } +// } +// _buyItem(player, toBuy, amt, customPrice); +// } + public static void buyItem(Player player, JItem toBuy, int amt) throws SQLException, Exception { + buyItem(player, new JItem[]{toBuy}, amt, -1); + } + + public static void buyItem(Player player, JItem toBuy, int amt, double customPrice) throws SQLException, Exception { + buyItem(player, new JItem[]{toBuy}, amt, customPrice); + } + + public static void buyItem(Player player, JItem[] toBuy, int amt, double customPrice) throws SQLException, Exception { + if (toBuy == null || toBuy.length == 0 || toBuy[0] == null || player == null || amt == 0) { + return; + } + Shop shop = BetterShop.getShop(player); + if (toBuy.length == 1) { + if (customPrice <= 0 && !shop.pricelist.canBuy(toBuy[0])) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("notforsale"). + replace("", toBuy[0].coloredName())); + return; + } + // check if there are items avaliable for purchase + long avail = -1; + if (BetterShop.getSettings().useItemStock) { + try { + avail = shop.stock.getItemAmount(toBuy[0]); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + avail = -1; + } + if (avail == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofstock"). + replace("", toBuy[0].coloredName())); + return; + } else if (avail >= 0 && amt > avail) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("lowstock"). + replace("", toBuy[0].coloredName()). + replace("", String.valueOf(avail))); + amt = (int) avail; + } + } + int canHold = shop.pricelist.getAmountCanBuy(player, toBuy[0], customPrice); + if (amt < 0) { + amt = canHold; + } else if (canHold >= 0 && amt > canHold) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofroom"). + replace("", toBuy[0].coloredName()). + replace("", String.valueOf(amt)). + replace("", String.format("%01.2f", + shop.pricelist.itemBuyPrice(player, toBuy[0], 1))). + replace("", String.valueOf(amt - canHold)). + replace("", BetterShop.getSettings().currency()). + replace("", String.valueOf(canHold))); + if (canHold <= 0) { + return; + } + amt = canHold; + } + _buyItem(player, toBuy, amt, customPrice); + } else { + List canBuy = new ArrayList(); + + String notWant = "", noStock = ""; + double bal = BSEcon.getBalance(player); + for (JItem i : toBuy) { + if (BetterShop.getPricelist(player.getLocation()).canBuy(i)) { + int buyamt = amt; + long avail = -1; + // check if heve enough to buy + if (BetterShop.getSettings().useItemStock) { + try { + avail = shop.stock.getItemAmount(i); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + avail = -1; + } + if (avail == 0) { + noStock += i.coloredName() + ", "; + continue; + } else if (avail > 0 && amt > avail) { + buyamt = (int) avail; + } + } + if (buyamt < 0) { + int canHold = shop.pricelist.getAmountCanBuy(player, bal, i, customPrice); + buyamt = canHold > avail ? (int) avail : canHold; + bal -= shop.pricelist.itemBuyPrice(player, i, buyamt); + } + if(buyamt > 0) { + canBuy.add(new ItemStockEntry(i, buyamt)); + } + } else if (i != null) { + notWant += i.coloredName() + ", "; + } + } + + if (!noStock.isEmpty()) { + noStock = noStock.substring(0, noStock.length() - 2); + if (noStock.contains(",")) { + noStock = "(" + noStock + ")"; + } + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofstock"). + replace("", noStock)); + } + + if (canBuy.isEmpty()) { + if (!notWant.isEmpty()) { + notWant = notWant.substring(0, notWant.length() - 2); + if (notWant.contains(",")) { + notWant = "(" + notWant + ")"; + } + BSutils.sendMessage(player, + BetterShop.getSettings().getString("notforsale"). + replace("", notWant)); + } + return; + } + + // else, find max. can hold + ItemStack start[] = ItemStackManip.copy(player.getInventory().getContents()), + result[] = ItemStackManip.copy(start); + List add = new ArrayList(); + for (ItemStockEntry u : canBuy) { + add.add(new ItemStack(u.itemNum, (int) u.amount)); + } + ItemStackManip.add(result, add, !BetterShop.getSettings().usemaxstack); + List diff = ItemStackManip.itemStackDifferences(start, result); + + String over = ""; + int overamt = 0, overleft = 0, overbuy = 0; + double buyprice = 0; + for (ItemStockEntry i : canBuy) { //ItemStack i : diff) { + int j = -1; + for (int n = 0; n < diff.size(); ++n) { + if (i.equals(diff.get(n))) { + j = n; + break; + } + } + if (j < 0) { + // not in diff: can't be added + over += JItemDB.GetItemColoredName(i.itemNum, i.itemSub) + ", "; + overamt += i.amount; + //buyprice += BetterShop.getPricelist(player.getLocation()).itemBuyPrice(player, toBuy[0], 1) * i.amount; + i.amount = 0; + } else { + if (i.amount > diff.get(j).getAmount()) { + over += JItemDB.GetItemColoredName(i.itemNum, i.itemSub) + ", "; + overamt += diff.get(j).getAmount(); + overleft += canBuy.get(j).amount - diff.get(j).getAmount(); + overbuy += (int) diff.get(j).getAmount(); + buyprice += BetterShop.getPricelist(player.getLocation()).itemBuyPrice(player, toBuy[0], 1) * (int) diff.get(j).getAmount(); + canBuy.get(j).amount = diff.get(j).getAmount(); + } + } + } + if (overamt > 0) { + over = over.substring(0, over.length() - 2); + if (over.contains(",")) { + over = "(" + over + ")"; + } + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofroom"). + replace("", over). + replace("", String.valueOf(overamt)). + replace("", String.format("%01.2f", buyprice / overbuy)). + replace("", String.valueOf(overleft)). + replace("", BetterShop.getSettings().currency()). + replace("", String.valueOf(overbuy))); + + if (overbuy <= 0) { + return; + } + } + + buyItem(player, canBuy, customPrice); + } + + } + + /* + * assumes that the amounts given are correct, and will try to purchase until out of room or money + */ + public static void buyItem(Player player, List buy, double customPrice) throws SQLException, Exception { + Shop shop = BetterShop.getShop(player.getLocation()); + PlayerInventory inv = player.getInventory(); + double price = 0; + String itemN = ""; + int amtBought = 0; + for (ItemStockEntry ite : buy) { + if (ite == null || ite.itemNum <= 0) { + continue; + } + JItem it = JItemDB.GetItem(ite.itemNum, (byte) ite.itemSub); + if (it == null) { + continue; + } + long maxAmt = shop.config.useStock() ? shop.stock.getItemAmount(it) : -1; + int buyAmt = maxAmt > 0 && ite.amount > maxAmt ? (int) maxAmt : (int) ite.amount; + double itemCost = customPrice >= 0 ? customPrice * buyAmt + : shop.pricelist.itemBuyPrice(player, it, buyAmt); + // buy + if (BSEcon.debit(player, itemCost)) { + amtBought += buyAmt; + price += itemCost; + itemN += it.coloredName() + ", "; + if (it.isEntity()) { + CreatureItem c = CreatureItem.getCreature(it.ID()); + if (c != null) { + for (int i = 0; i < buyAmt; ++i) { + c.spawnNewWithOwner(player); + } + } + } else { + if (it.equals(JItems.MAP)) { + //TODO: either make a new map or copy a map..... + } + if (!ItemStackManip.canHold(player.getInventory().getContents(), it, buyAmt, !BetterShop.getSettings().usemaxstack)) { + BSutils.sendMessage(player, ChatColor.RED + "Program Error: tried to buy more than can hold: aborting."); + System.out.println(ChatColor.RED + "Program Error: tried to buy more than can hold: " + it + " x" + buyAmt); + BSEcon.credit(player, itemCost); + break; + } + inv.setContents(ItemStackManip.add(player.getInventory().getContents(), + it, buyAmt, !BetterShop.getSettings().usemaxstack)); + } + + try { + if (BetterShop.getSettings().useItemStock) { + shop.stock.changeItemAmount(it, -buyAmt); + } + if (BetterShop.getSettings().logUserTransactions) { + shop.transactions.addRecord(new UserTransaction( + it, false, buyAmt, price / buyAmt, player.getDisplayName())); + } + + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } else { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("insuffunds"). + replace("", it.coloredName()). + replace("", String.valueOf(buyAmt)). + replace("", String.valueOf(price)). + replace("", BetterShop.getSettings().currency()). + replace("", String.valueOf(price / buyAmt)). + replace("", BSEcon.format(price))); + break; + } + } + + if (itemN.length() > 0) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + + BSutils.sendFormttedMessage(player, "buymsg", itemN, amtBought, price); + } + + /** + * assumes items can be bought, and have the correct amount(s) + */ + private static void _buyItem(Player player, JItem[] toBuy, int amt, double customPrice) throws SQLException, Exception { + Shop shop = BetterShop.getShop(player.getLocation()); + PlayerInventory inv = player.getInventory(); + double price = 0;// = customPrice >= 0 ? customPrice * amt : shop.pricelist.itemBuyPrice(player, toBuy, amt); + String itemN = ""; + int amtBought = 0; + for (JItem it : toBuy) { + if (it == null) { + continue; + } + long maxAmt = shop.config.useStock() ? shop.stock.getItemAmount(it) : -1; + int buyAmt = maxAmt > 0 && amt > maxAmt ? (int) maxAmt : amt; + amtBought += buyAmt; + double itemCost = customPrice >= 0 ? customPrice * buyAmt + : shop.pricelist.itemBuyPrice(player, it, buyAmt); + price += itemCost; + if (itemCost < 0) { + throw new Exception("Invalid Price encountered: " + itemCost + " for " + amt + " " + it.Name()); + } + if (BSEcon.debit(player, itemCost)) { + itemN += it.coloredName() + ", "; + if (it.isEntity()) { + CreatureItem c = CreatureItem.getCreature(it.ID()); + if (c != null) { + for (int i = 0; i < buyAmt; ++i) { + c.spawnNewWithOwner(player); + } + } + } else { + if (it.equals(JItems.MAP)) { + //TODO: either make a new map or copy a map..... + } + inv.setContents(ItemStackManip.add(player.getInventory().getContents(), + it, buyAmt, !BetterShop.getSettings().usemaxstack)); + } + + try { + if (BetterShop.getSettings().useItemStock) { + shop.stock.changeItemAmount(it, -buyAmt); + } + if (BetterShop.getSettings().logUserTransactions) { + shop.transactions.addRecord(new UserTransaction( + it, false, buyAmt, price / buyAmt, player.getDisplayName())); + } + + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } else { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("insuffunds"). + replace("", it.coloredName()). + replace("", String.valueOf(amt)). + replace("", String.valueOf(price)). + replace("", BetterShop.getSettings().currency()). + replace("", String.valueOf(price / amt)). + replace("", BSEcon.format(price))); + break; + } + } + + if (itemN.length() > 0) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + + BSutils.sendFormttedMessage(player, "buymsg", itemN, amtBought, price); + } +} // end class BuyCommands + diff --git a/src/me/jascotty2/bettershop/commands/ChestCommands.java b/src/me/jascotty2/bettershop/commands/ChestCommands.java new file mode 100644 index 0000000..560cf38 --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/ChestCommands.java @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( TODO ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.NestedCommand; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ChestCommands { + + @Command(commands = {}, + aliases = {"chest", "ch"}, + desc = "General shop chest commands") + @NestedCommand({ChestCommands.Commands.class}) + public static void chest(CommandSender sender, String[] args) { + } + + public static class Commands { + + @Command(commands = {}, + aliases = {"define", "d", "create", "c"}, + desc = "Define a Chest shop", + usage = "", + permissions = {"BetterShop.admin.chests"}) + public static void define(CommandSender player, String[] s) { + if (BSutils.anonymousCheck(player)) { + return; + } + Player p = (Player) player; + Block b = p.getTargetBlock(null, 5); + if (b.getType() == Material.CHEST) { + if (BetterShop.getChestShop().defineChestShop(b)) { + BSutils.sendMessage(p, "Chest is now a Shop"); + } else { + BSutils.sendMessage(p, ChatColor.RED + "Chest is already a Shop"); + } + } else { + BSutils.sendMessage(p, ChatColor.RED + "This is not a Chest!"); + } + } + + @Command(commands = {}, + aliases = {"edit", "e"}, + desc = "Change Items in a Chest Shop", + usage = "", + permissions = {"BetterShop.admin.chests"}) + public static void edit(CommandSender player, String[] s) { + if (BSutils.anonymousCheck(player)) { + return; + } + Player p = (Player) player; + Block b = p.getTargetBlock(null, 5); + if (b.getType() == Material.CHEST) { + if (BetterShop.getChestShop().hasChestShop(b)) { + BetterShop.getChestShop().openChest(p, (Chest) b.getState(), true); + } else { + BSutils.sendMessage(p, ChatColor.RED + "This is not a Chest Shop"); + } + } else { + BSutils.sendMessage(p, ChatColor.RED + "This is not a Chest!"); + } + } + + @Command(commands = {}, + aliases = {"remove", "r", "delete", "del"}, + desc = "Remove a Chest Shop", + usage = "", + permissions = {"BetterShop.admin.chests"}) + public static void remove(CommandSender player, String[] s) { + if (BSutils.anonymousCheck(player)) { + return; + } + Player p = (Player) player; + Block b = p.getTargetBlock(null, 5); + if (b.getType() == Material.CHEST) { + if (BetterShop.getChestShop().removeChestShop(b)) { + BSutils.sendMessage(p, "Chest is no longer a Shop"); + } else { + BSutils.sendMessage(p, ChatColor.RED + "This is not a Chest Shop"); + } + } else { + BSutils.sendMessage(p, ChatColor.RED + "This is not a Chest!"); + } + } + } +} // end class ChestCommands + diff --git a/src/me/jascotty2/bettershop/commands/HelpCommands.java b/src/me/jascotty2/bettershop/commands/HelpCommands.java new file mode 100644 index 0000000..bf9ca09 --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/HelpCommands.java @@ -0,0 +1,265 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods related to plugin helps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.commands; + +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.util.Str; +//import me.taylorkelly.help.Help; +import me.taylorkelly.help.Help; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +/** + * @author jacob + */ +public class HelpCommands { + + public static boolean helpPluginEnabled = false; // no reason to check twice :) + + @Command( + commands = {"shophelp", "shelp"}, + aliases = {"help", "?"}, + desc = "Lists available commands", + permissions = {"BetterShop.user.help"}) + public static boolean help(CommandSender player, String[] s) { + if(s.length > 0 && s[0].toLowerCase().contains("help")){ + String[] newArgs = new String[s.length - 1]; + System.arraycopy(s, 1, newArgs, 0, newArgs.length); + s = newArgs; + } + if (s.length > 0) { + // extra command help + + } + + if (s.length > 0) { + // more help + if (Str.isIn(s[0], "shop")) { + BSutils.sendMessage(player, "/shop command alias to other commands"); + BSutils.sendMessage(player, " "); + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_BACKUP, false)) { + BSutils.sendMessage(player, "/shop backup to backup current pricelist"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_INFO, false)) { + BSutils.sendMessage(player, "/shop ver[sion] show the currently installed version"); + BSutils.sendMessage(player, " also shows if this is the most current avaliable"); + } + } else if (Str.isIn(s[0], "shopcheck, scheck, sc")) { + BSutils.sendMessage(player, "/shopcheck Check prices for an item"); + BSutils.sendMessage(player, " aliases: scheck, sc"); + BSutils.sendMessage(player, "-- will also run a name match search"); + } else if (Str.isIn(s[0], "shoplist, slist, sl")) { + BSutils.sendMessage(player, "/shoplist [page] Lists prices for the shop"); + BSutils.sendMessage(player, " aliases: slist, sl"); + } else if (Str.isIn(s[0], "shopitems, sitems")) { + BSutils.sendMessage(player, "/shopitems show listing of items in shop, without prices"); + BSutils.sendMessage(player, " aliases: sitems"); + BSutils.sendMessage(player, "-- coming soon: pages"); + } else if (Str.isIn(s[0], "shopbuy, sbuy, buy")) { + BSutils.sendMessage(player, "/shopbuy [amount] Buy an item for the price in the shop"); + BSutils.sendMessage(player, " aliases: sbuy, buy"); + BSutils.sendMessage(player, "-- \"all\" is a valid amount: will buy all you can hold/afford"); + } else if (Str.isIn(s[0], "shopbuyall, sbuyall, buyall")) { + BSutils.sendMessage(player, "/shopbuyall buy all you can hold/afford"); + BSutils.sendMessage(player, " aliases: sbuyall, buyall"); + } else if (Str.isIn(s[0], "shopbuystack, buystack, sbuystack, sbuys, buys")) { + BSutils.sendMessage(player, "/shopbuystack [amount] buy items in stacks"); + BSutils.sendMessage(player, " aliases: buystack, sbuystack, sbuys, buys"); + BSutils.sendMessage(player, "-- can list multiple items, or give how many stacks to buy"); + } else if (Str.isIn(s[0], "shopsell, ssell, sell")) { + BSutils.sendMessage(player, "/shopsell [amount]"); + BSutils.sendMessage(player, " aliases: ssell, sell"); + BSutils.sendMessage(player, "-- \"all\" is a valid amount: will sell all you have"); + } else if (Str.isIn(s[0], "shopsellall, sellall, sell all")) { + BSutils.sendMessage(player, "/shopsellall [inv] [item [item [...]]] "); + BSutils.sendMessage(player, "-- Sell all of item from your inventory"); + BSutils.sendMessage(player, " aliases: sellall, sell all"); + BSutils.sendMessage(player, "-- inv will only sell from your inventory, not the lower 9"); + BSutils.sendMessage(player, "-- multiple items can be listed, or none for all sellable"); + } else if (Str.isIn(s[0], "shopadd, sadd")) { + BSutils.sendMessage(player, "/shopadd [sellprice]"); + BSutils.sendMessage(player, "-- Add an item to or update an item in the price list"); + BSutils.sendMessage(player, " aliases: sadd"); + BSutils.sendMessage(player, "-- price of -1 disables that action"); + BSutils.sendMessage(player, "-- if no sellprice is given, item will not be sellable"); + } else if (Str.isIn(s[0], "shopremove, sremove")) { + BSutils.sendMessage(player, "/shopremove Remove an item from the price list"); + BSutils.sendMessage(player, " aliases: sremove"); + } else if (Str.isIn(s[0], "shopload, sload, shop load, shop reload")) { + BSutils.sendMessage(player, "/shopload reload prices from pricelist database"); + BSutils.sendMessage(player, " aliases: sload, shop [re]load"); + } else if (Str.isIn(s[0], "shophelp, shelp")) { + BSutils.sendMessage(player, "/shophelp [command] Lists available commands"); + BSutils.sendMessage(player, " aliases: shelp"); + BSutils.sendMessage(player, "-- providing a command shows specific help for that command"); + } else if (Str.isIn(s[0], "shoplistkits, shopkits, skits")) { + BSutils.sendMessage(player, "/shoplistkits [page] Lists available kits"); + BSutils.sendMessage(player, " aliases: shopkits, skits"); + BSutils.sendMessage(player, "-- toadd: show what each kit contains"); + } else if (Str.isIn(s[0], "shopbuyagain, sbuyagain, buyagain, sba")) { + BSutils.sendMessage(player, "/shopbuyagain repeat last purchase"); + BSutils.sendMessage(player, " aliases: sbuyagain, buyagain, sba"); + } else if (Str.isIn(s[0], "shopsellagain, ssellagain, sellagain, ssa")) { + BSutils.sendMessage(player, "/shopsellagain repeat last sale"); + BSutils.sendMessage(player, " aliases: ssellagain, sellagain, ssa"); + } /*else if (s[0].equalsIgnoreCase("")) { + BSutils.sendMessage(player, "/"); + BSutils.sendMessage(player, " aliases: "); + BSutils.sendMessage(player, "-- "); + } */ else { + BSutils.sendMessage(player, "Unknown Help Topic"); + } + return true; + } + BSutils.sendMessage(player, "--------- Better Shop Usage --------"); + if (BSPermissions.hasPermission(player, BetterShopPermission.USER_LIST, false)) { + BSutils.sendMessage(player, "/shoplist [page] - List shop prices"); + BSutils.sendMessage(player, "/shopitems - show listing of items in shop, without prices"); + BSutils.sendMessage(player, "/shopkits [page] - show listing of kits in shop"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.USER_BUY, false)) { + BSutils.sendMessage(player, "/shopbuy [amount] - Buy items"); + BSutils.sendMessage(player, "/shopbuyall - Buy all that you can hold/afford"); + BSutils.sendMessage(player, "/shopbuystack [amount] - Buy stacks of items"); + BSutils.sendMessage(player, "/shopbuyagain - repeat last purchase action"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.USER_SELL, false)) { + BSutils.sendMessage(player, "/shopsell [amount] - Sell items "); + BSutils.sendMessage(player, "/shopsellall - Sell all of your items"); + BSutils.sendMessage(player, "/shopsellstack [amount] - Sell stacks of items"); + BSutils.sendMessage(player, "/shopsellagain - Repeat last sell action"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.USER_CHECK, false)) { + BSutils.sendMessage(player, "/shopcheck [amount] - Check prices of an item"); + } + BSutils.sendMessage(player, "/shophelp [command] - show help on commands"); + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN, false)) { + BSutils.sendMessage(player, "**-------- Admin commands --------**"); + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ADD, false)) { + BSutils.sendMessage(player, "/shopadd <$buy> [$sell] - Add/Update an item"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_REMOVE, false)) { + BSutils.sendMessage(player, "/shopremove - Remove an item from the shop"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_LOAD, false)) { + BSutils.sendMessage(player, "/shopload - Reload the Configuration & PriceList DB"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_RESTOCK, false)){ + BSutils.sendMessage(player, "/shop restock - restock items to starting values"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_BACKUP, false)){ + BSutils.sendMessage(player, "/shop backup [file] - Backup the pricelist to a csv file"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_BACKUP, false)){ + BSutils.sendMessage(player, "/shop restore - restore a pricelist from a csv file"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_BACKUP, false)){ + BSutils.sendMessage(player, "/shop import - import items to pricelist from a csv file"); + } + if (BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_INFO, false)){ + BSutils.sendMessage(player, "/shop version - check the current version & if there are updates avaliable"); + } + if(player.isOp()){ + BSutils.sendMessage(player, "/shop update - Download & Install an Update"); + } + } + BSutils.sendMessage(player, "----------------------------------"); + return true; + } + + public static void registerHelp(Plugin p) { + if (!helpPluginEnabled && p != null && p instanceof Help) { + Help helpPlugin = (Help)p; + Plugin plugin = BetterShop.getPlugin(); + BSConfig config = BetterShop.getSettings(); + helpPlugin.registerCommand("shoplist [page]", + "List shop prices", plugin, !config.hideHelp, + "BetterShop.user.list"); + helpPlugin.registerCommand("shopitems", + "compact listing of items in shop", plugin, + "BetterShop.user.list"); + helpPlugin.registerCommand("shopkits [page]", + "show listing of kits in shop", plugin, + "BetterShop.user.list"); + helpPlugin.registerCommand("shopbuy [item] ", + "Buy items from the shop", plugin, !config.hideHelp, + "BetterShop.user.buy"); + helpPlugin.registerCommand("shopbuyall [item]", + "Buy all that you can hold/afford", plugin, + "BetterShop.user.buy"); + helpPlugin.registerCommand("shopbuystack [item] ", + "Buy stacks of items", plugin, "BetterShop.user.buy"); + helpPlugin.registerCommand("shopbuyagain", + "repeat last purchase action", plugin, + "BetterShop.user.buy"); + helpPlugin.registerCommand("shopsell [item] ", + "Sell items to the shop", plugin, !config.hideHelp, + "BetterShop.user.sell"); + helpPlugin.registerCommand("shopsellstack [item] ", + "Sell stacks of items", plugin, "BetterShop.user.sell"); + helpPlugin.registerCommand("shopsellall ", + "Sell all of your items", plugin, "BetterShop.user.sell"); + helpPlugin.registerCommand("shopsellagain", + "Repeat last sell action", plugin, + "BetterShop.user.sell"); + helpPlugin.registerCommand("shopcheck [item]", + "Check prices of item[s]", plugin, !config.hideHelp, + "BetterShop.user.check"); + helpPlugin.registerCommand("shophelp [command]", + "show help on commands", plugin, !config.hideHelp, + "BetterShop.user.help"); + helpPlugin.registerCommand("shopadd [item] [$buy] <$sell>", + "Add/Update an item", plugin, !config.hideHelp, + "BetterShop.admin.add"); + helpPlugin.registerCommand("shopremove [item]", + "Remove an item from the shop", plugin, !config.hideHelp, + "BetterShop.admin.remove"); + helpPlugin.registerCommand("shopload", + "Reload the Configuration & PriceList DB", plugin, + !config.hideHelp, "BetterShop.admin.load"); + + helpPlugin.registerCommand("shop restock", + "manually restock (if enabled)", plugin, + "BetterShop.admin.restock"); + helpPlugin.registerCommand("shop ver[sion]", + "Show Version # and if is current", plugin, + "BetterShop.admin.info"); + helpPlugin.registerCommand("shop backup", + "backup current pricelist", plugin, + "BetterShop.admin.backup"); + helpPlugin.registerCommand("shop import [file]", + "import a file into the pricelist", plugin, + "BetterShop.admin.backup"); + helpPlugin.registerCommand("shop restore [file]", + "restore pricelist from backup", plugin, + "BetterShop.admin.backup"); + helpPlugin.registerCommand("shop update", + "manually update bettershop to newest version", plugin, + "OP"); + } // else Log("HelpCommands not yet found."); + helpPluginEnabled = p != null; + } + +} // end class HelpCommands + diff --git a/src/me/jascotty2/bettershop/commands/ListCommands.java b/src/me/jascotty2/bettershop/commands/ListCommands.java new file mode 100644 index 0000000..1426550 --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/ListCommands.java @@ -0,0 +1,305 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods for viewing items & info in the shop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.commands; + +import java.util.List; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.MinecraftChatStr; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.PriceListItem; +import me.jascotty2.lib.io.CheckInput; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class ListCommands { + + @Command(commands = {"shoplist", "slist", "sl"}, + aliases = {"list", "l", "ls"}, + desc = "Lists prices for the shop", + permissions = {"BetterShop.user.list"}) + public static boolean list(CommandSender player, String[] s) { + int pagenum = 1; + if ((s.length > 1)) { + return false; + } else if (s.length == 1) { + if (s[0].equalsIgnoreCase("full") || s[0].equalsIgnoreCase("all")) { + pagenum = -1; + } else if (s[0].equalsIgnoreCase("item") || s[0].equalsIgnoreCase("items")) { + return listitems(player, null); + } else if (s[0].equalsIgnoreCase("kits")) { + return listkits(player, null); + } else if (!CheckInput.IsInt(s[0])) { + BSutils.sendMessage(player, "That's not a page number."); + return false; + } else { + pagenum = CheckInput.GetInt(s[0], 1); + } + } + + Shop shop = BetterShop.getShop(player); + if (shop == null) { + BSutils.sendMessage(player, ChatColor.RED + "Pricelist Error: Notify Server Admin"); + } else { + for (String line : shop.pricelist.GetShopListPage(pagenum, player, shop.stock)) { + BSutils.sendMessage(player, + line.replace("", BetterShop.getSettings().currency())); + } + } + + return true; + } + + @Command(commands = {"shoplistitems", "shopitems", "sitems", "slistitems"}, + aliases = {"items", "i", "li", "listitems"}, + desc = "Lists items registered in the shop", + permissions = {"BetterShop.user.list"}) + public static boolean listitems(CommandSender player, String[] s) { + if (s != null && s.length > 1) { + return false; + } + try { + List items = BetterShop.getPricelist(player).getItemList( + BetterShop.getSettings().allowbuyillegal || BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false)); + StringBuilder output = new StringBuilder("\u00A72"); + if (items != null && items.size() > 0) { + for (int i = 0; i < items.size(); ++i) { + output.append(items.get(i)); + if (i + 1 < items.size()) { + output.append("\u00A72, "); + } + } + } + BSutils.sendMessage(player, output.toString()); + return true; + } catch (Exception ex) { + BetterShopLogger.Severe(ex); + } + + BSutils.sendMessage(player, ChatColor.RED + "An Error Occurred while looking up an item.. attemping to reload.."); + if (AdminCommands.load(null, null)) { + // ask to try command again.. don't want accidental infinite recursion & don't want to plan for recursion right now + BSutils.sendMessage(player, "Success! Please try again.. "); + } else { + BSutils.sendMessage(player, ChatColor.RED + "Failed! Please let an OP know of this error"); + } + return true; + } + + @Command( + commands = {"shoplistkits", "shopkits", "skits", "slistits"}, + aliases = {"kits", "k", "listkits"}, + desc = "Lists kits in the shop", + permissions = {"BetterShop.user.list"}) + public static boolean listkits(CommandSender player, String[] s) { + try { + BSutils.sendMessage(player, "Kit listing:"); + String kitNames = ""; + for (JItem i : BetterShop.getPricelist(player).getItems(BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL))) { + if (i.isKit()) { + if (kitNames.length() > 0) { + kitNames += ", "; + } + kitNames += i.coloredName(); + } + } + BSutils.sendMessage(player, kitNames); + return true; + } catch (Exception ex) { + BetterShopLogger.Severe(ex); + BSutils.sendMessage(player, "Error looking up an item.. Attempting DB reload.."); + if (AdminCommands.load(null, null)) { + // ask to try command again.. don't want accidental infinite recursion & don't want to plan for recursion right now + BSutils.sendMessage(player, "Success! Please try again.. "); + } else { + BSutils.sendMessage(player, ChatColor.RED + "Failed! Please let an OP know of this error"); + } + return true; + } + } + + @Command( + commands = {"shoplistalias", "shopalias", "salias", "sa"}, + aliases = {"alias", "a", "listalias", "aliases", "listaliases"}, + desc = "Show the accepted aliases for an item", + permissions = {"BetterShop.user.help"}) + public static boolean listAlias(CommandSender player, String[] s) { + if (s.length != 1) { + return false; + } + JItem it = JItemDB.findItem(s[0]); + if (it == null) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem"). + replace("", s[0])); + } else { + StringBuilder aliases = new StringBuilder(); + for (String a : it.Aliases()) { + aliases.append(a).append(", "); + } + if (aliases.length() > 1) {//.indexOf(",") != -1) { + aliases.delete(aliases.length() - 2, aliases.length()); + } + + BSutils.sendMessage(player, + MinecraftChatStr.strWordWrap( + BetterShop.getSettings().getString("listalias"). + replace("", it.coloredName()).replace("", aliases.toString()))); + } + return true; + } + + @Command( + commands = {"shopcheck", "scheck", "sc"}, + aliases = {"check", "c", "price", "lookup"}, + desc = "Check prices for an item", + permissions = {"BetterShop.user.check"}) + public static boolean check(CommandSender player, String[] s) { + if (s == null || s.length == 0 || s.length > 2) { + return false; + } else if (s.length > 1 && !CheckInput.IsInt(s[1]) && !s[1].equalsIgnoreCase("all")) { + BSutils.sendMessage(player, "Invalid amount"); + return true; + } + + boolean canBuyIllegal = BetterShop.getSettings().allowbuyillegal + || BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false); + + Shop shop = BetterShop.getShop(player); + + try { + if (s[0].equalsIgnoreCase("all")) { + List sellable = SellCommands.getCanSell((Player) player, false, null, -1); + if (!sellable.isEmpty()) { + PriceListItem price = new PriceListItem(); + price.buy = price.sell = 0; + String name = "("; // price.name = "("; + int numCheck = 0; + for (ItemStack ite : sellable) { + numCheck += ite.getAmount(); + JItem it = JItemDB.findItem(ite); + PriceListItem tprice = shop.pricelist.getItemPrice(it); + if (tprice != null) { + price.buy += tprice.buy > 0 ? tprice.buy : 0; + price.sell += tprice.sell > 0 ? tprice.sell : 0; + if (name.length() > 1) { + name += ", " + it.coloredName(); + } else { + name += it.coloredName(); + } + } + } + name += ")"; + BSutils.sendMessage(player, String.format( + BetterShop.getSettings().getString(numCheck == 1 ? "pricecheck" : "multipricecheck"). + replace("", "%1$s"). + replace("", "%2$s"). + replace("", "%3$s"). + replace("", "%4$s"). + replace("", "%5$s"). + replace("", "%6$s"). + replace("", "%7$s"). + replace("", "%8$s"), + (price.IsLegal() || canBuyIllegal) && price.buy >= 0 ? price.buy : "No", + price.sell >= 0 ? price.sell : "No", + name, + BetterShop.getSettings().currency(), + (price.IsLegal() || canBuyIllegal) && price.buy >= 0 + ? BSEcon.format(price.buy) : "No", + price.sell >= 0 ? BSEcon.format(price.sell) : "No", + "?", + numCheck)); + } else { + BSutils.sendMessage(player, "no sellabel items in your inventory"); + } + return true; + } else { + JItem lookup[] = JItemDB.findItems(s[0]); + if (lookup == null || lookup.length == 0 || lookup[0] == null) { + lookup = JItemDB.getItemsByCategory(s[0]); + if (lookup == null || lookup.length == 0 || lookup[0] == null) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem"). + replace("", s[0])); + return true; + } + } + + int inStore = 0, + numCheck = s.length > 1 && !s[1].equalsIgnoreCase("all") ? CheckInput.GetInt(s[1], 1) : 1; + for (JItem i : lookup) { + PriceListItem price = shop.pricelist.getItemPrice(i); + if (price != null) { + ++inStore; + BSutils.sendMessage(player, String.format( + BetterShop.getSettings().getString(numCheck == 1 ? "pricecheck" : "multipricecheck"). + replace("", "%1$s"). + replace("", "%2$s"). + replace("", "%3$s"). + replace("", "%4$s"). + replace("", "%5$s"). + replace("", "%6$s"). + replace("", "%7$s"). + replace("", "%8$s"), + (price.IsLegal() || canBuyIllegal) && price.buy >= 0 ? numCheck * price.buy : "No", + price.sell >= 0 ? numCheck * price.sell : "No", + i.coloredName(), + BetterShop.getSettings().currency(), + (price.IsLegal() || canBuyIllegal) && price.buy >= 0 + ? BSEcon.format(numCheck * price.buy) : "No", + price.sell >= 0 ? BSEcon.format(numCheck * price.sell) : "No", + !shop.config.useStock() || shop.stock.getItemAmount(i) < 0 ? "INF" : shop.stock.getItemAmount(i), + numCheck)); + + } else if (lookup.length <= 5) { // only show nolisting if result page is 5 or less lines + BSutils.sendMessage(player, + String.format(BetterShop.getSettings().getString("nolisting"). + replace("", "%s"), i.coloredName())); + } + } + if (lookup.length > 5 && inStore == 0) { + BSutils.sendMessage(player, String.format("No Sellable items found under \"%s\"", s[0])); + } + return true; + + } + } catch (Exception ex) { + BetterShopLogger.Severe(ex); + } + BSutils.sendMessage(player, ChatColor.RED + "An Error Occurred while looking up an item.. attemping to reload.."); + if (AdminCommands.load(null, null)) { + // ask to try command again.. don't want accidental infinite recursion & don't want to plan for recursion right now + BSutils.sendMessage(player, "Success! Please try again.. "); + } else { + BSutils.sendMessage(player, ChatColor.RED + "Failed! Please let an OP know of this error"); + } + return true; + } +} // end class ListCommands + diff --git a/src/me/jascotty2/bettershop/commands/RegionCommands.java b/src/me/jascotty2/bettershop/commands/RegionCommands.java new file mode 100644 index 0000000..9ce760d --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/RegionCommands.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods related to shop regions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.regionshops.RegionShopManager; +import me.jascotty2.lib.bukkit.MinecraftChatStr; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.NestedCommand; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.util.Str; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * @author jacob + */ +public class RegionCommands { + + @Command(commands = {}, + aliases = {"region", "r"}, + desc = "General shop region commands") + @NestedCommand({RegionCommands.Commands.class}) + public static void region(CommandSender sender, String[] args) { + } + + public static class Commands { + + @Command(commands = {}, + aliases = {"define", "d"}, + desc = "Define a Shop Region", + usage = "", + min = 1, + permissions = {"BetterShop.admin.region"}) + public static void define(CommandSender sender, String[] args) { + if (BSutils.anonymousCheck(sender)) { + return; + } + if (!BetterShop.getShopManager().addRegion(args[0], (Player) sender)) { + BSutils.sendMessage(sender, ChatColor.RED + "Could not save shop region"); + } + } + + @Command(commands = {}, + aliases = {"remove", "r"}, + desc = "Remove a Shop Region", + usage = "", + min = 1, + permissions = {"BetterShop.admin.region"}) + public static void remove(CommandSender sender, String[] args) { + BetterShop.getShopManager().removeRegion(sender, (args[0])); + } + + @Command(commands = {}, + aliases = {"list", "ls", "l"}, + desc = "List Shop Regions", + usage = "[page]", + max = 1, + permissions = {"BetterShop.admin.region"}) + public static void list(CommandSender sender, String[] args) { + RegionShopManager s = BetterShop.getShopManager(); + int max = s.numRegions(), + page = args.length > 0 ? CheckInput.GetInt(args[0], 0) - 1 : 0, + pagesize = BetterShop.getSettings().pagesize, + pages = (int) Math.ceil((double)max / pagesize); + if (page < 0 || page > pages) { + BSutils.sendMessage(sender, args[0] + " is not a valid page number (there are " + pages + " pages)"); + return; + } + if (sender instanceof Player) { + BSutils.sendMessage(sender, MinecraftChatStr.padCenter( + " Shop Regions page " + (page+1) + "/" + pages + " ", '-', MinecraftChatStr.chatwidth - MinecraftChatStr.getStringWidth(BetterShop.getSettings().getString("prefix")))); + } else { + BSutils.sendMessage(sender, Str.padCenter( + " Shop Regions page " + (page+1) + "/" + pages + " ", + 80 - MinecraftChatStr.getStringWidth(BetterShop.getSettings().getString("prefix")), '-')); + } + for (String l : BetterShop.getShopManager().getRegionList( + (sender instanceof Player ? ((Player) sender).getWorld() : null), + page, pagesize)) { + BSutils.sendMessage(sender, l); + } + } + } +} // end class RegionCommands + diff --git a/src/me/jascotty2/bettershop/commands/SellCommands.java b/src/me/jascotty2/bettershop/commands/SellCommands.java new file mode 100644 index 0000000..4dc599f --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/SellCommands.java @@ -0,0 +1,524 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: commands & methods for selling + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.WrappedCommandException; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.shop.UserTransaction; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.util.ArrayManip; +import me.jascotty2.lib.util.Str; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class SellCommands { + + static Map usersellHistory = new HashMap(); + + @Command(commands = {"shopsell", "ssell", "sell"}, + aliases = {"sell", "s"}, + desc = "Sell an item for the price in the shop", + usage = " [amount]", + min = 1, + max = 2, + permissions = {"BetterShop.user.sell"}) + public static void sell(CommandSender player, String[] s) throws WrappedCommandException { + if (BSutils.anonymousCheck(player)) { + return; + } // "sell all", "sell all [item]" moved to own method ("sell [item] all" kept here) + else if (s.length >= 1 && s[0].equalsIgnoreCase("all")) { + String[] newArgs = new String[s.length - 1]; + System.arraycopy(s, 1, newArgs, 0, newArgs.length); + sellall(player, newArgs); + return; + } else if (s.length >= 2 && s[s.length - 1].equalsIgnoreCase("all")) { + String[] newArgs = new String[s.length - 1]; + System.arraycopy(s, 0, newArgs, 0, newArgs.length); + sellall(player, newArgs); + return; + } else if (s.length == 2 && CheckInput.IsInt(s[0]) && !CheckInput.IsInt(s[1])) { + // "sell ## item" + // swap two indicies + String t = s[0]; + s[0] = s[1]; + s[1] = t; + } else if (s.length == 0 || s.length > 2) { + return; + }// initial check complete: set as last action + usersellHistory.put(((Player) player).getDisplayName(), "shopsell " + Str.concatStr(s, " ")); + // expected syntax: item [amount] + + JItem toSell = JItemDB.isCategory(s[0]) ? null : JItemDB.findItem(s[0]), sellCat[] = null; + if (toSell == null) { + sellCat = JItemDB.getItemsByCategory(s[0]); + if (sellCat == null || sellCat.length == 0) { + // TODO: run partial match on items in inventory + // eg. '/sell sword' works if have one (type of) sword + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem"). + replace("", s[0])); + return; + } + } else if (toSell.ID() == 0) { + BSutils.sendMessage(player, toSell.coloredName() + " Cannot be Sold");//, toSell.coloredName()); + return; + } else if (toSell.isKit()) { + BSutils.sendMessage(player, "Kits cannot be sold"); + return; + } else if (toSell.isEntity()) { + BSutils.sendMessage(player, "Entities cannot be sold"); + return; + } else { + try { + if (!BetterShop.getShop((Player) player).pricelist.isForSale(toSell)) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("donotwant"). + replace("", toSell.coloredName())); + return; + } + } catch (Exception e) { + throw new WrappedCommandException(e); + } + } + + int amtSell = 1; + if (s.length > 1) { + if (!CheckInput.IsInt(s[1])) { + BSutils.sendMessage(player, s[1] + " is definitely not a number."); + } + amtSell = CheckInput.GetInt(s[1], 0); + } + + if (amtSell <= 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("nicetry")); + return; + } + try { + if (toSell == null) { + sellItems((Player) player, false, sellCat, amtSell); + } else { + sellItems((Player) player, false, toSell, amtSell); + } + } catch (Exception ex) { + throw new WrappedCommandException(ex); + } + } + + @Command(commands = {"shopsellstack", "sellstack", "ssellstack", "sells", "ssells"}, + aliases = {"sellstack"}, + desc = "Sell a stack of an item to the shop", + usage = " [amount]", + min = 1, + permissions = {"BetterShop.user.sell"}) + public static boolean sellstack(CommandSender player, String[] s) throws WrappedCommandException { + if (BSutils.anonymousCheck(player)) { + return true; + } + if (s.length == 2 && CheckInput.IsInt(s[1])) { + + JItem toSell = JItemDB.findItem(s[0]); + if (toSell == null) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem"). + replace("", s[0])); + return true; + } else if (!BetterShop.getSettings().allowbuyillegal && !toSell.IsLegal() + && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false)) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("illegalbuy"). + replace("", toSell.coloredName())); + return true; + } + // sell max. stackable + sell(player, new String[]{toSell.IdDatStr(), + String.valueOf((BetterShop.getSettings().usemaxstack ? toSell.getMaxStackSize() : 64) * CheckInput.GetInt(s[1], 1))}); + } else { + for (String is : s) { + JItem toSell = JItemDB.findItem(is); + if (toSell == null) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("unkitem"). + replace("", is)); + return true; + } else if (!BetterShop.getSettings().allowbuyillegal && !toSell.IsLegal() + && !BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false)) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("illegalbuy"). + replace("", toSell.coloredName())); + return true; + } + // sell max. stackable + sell(player, new String[]{toSell.IdDatStr(), String.valueOf( + BetterShop.getSettings().usemaxstack ? toSell.getMaxStackSize() : 64)}); + } + }// overwrite history that selll wrote + usersellHistory.put(((Player) player).getDisplayName(), "shopsellstack " + Str.concatStr(s)); + return true; + } + + @Command(commands = {"shopsellall", "sellall"}, + aliases = {"sellall", "sall"}, + desc = "Sell a stack of an item to the shop", + usage = "[item] [amount]", + permissions = {"BetterShop.user.sell"}) + public static void sellall(CommandSender player, String[] s) throws WrappedCommandException { + if (BSutils.anonymousCheck(player)) { + return; + } + JItem toSell[] = null; + boolean onlyInv = false; + if (s != null) { + if (s.length > 0) { + // expected syntax: [inv] [item [item [item [...]]]] + int st = 0; + if (s[0].equalsIgnoreCase("inv")) { + onlyInv = true; + st = 1; + } + //ArrayList sell = new ArrayList(); + toSell = new JItem[s.length - st]; + for (int i = st; i < s.length; ++i) { + toSell[i - st] = JItemDB.isCategory(s[i]) ? null : JItemDB.findItem(s[i]); + if (toSell[i - st] == null) { + JItem cts[] = JItemDB.getItemsByCategory(s[i]); + if (cts != null && cts.length > 0) { + toSell = ArrayManip.arrayConcat(toSell, cts); + //--i; + } else { + BSutils.sendMessage(player, String.format( + BetterShop.getSettings().getString("unkitem"). + replace("", "%1$s"), s[i])); + toSell[i - st] = null; + } + } else if (toSell[i - st].ID() == 0) { + BSutils.sendMessage(player, toSell[i - st].coloredName() + " Cannot be Sold"); // toSell[i - st].coloredName() + toSell[i - st] = null; + } else if (toSell[i - st].isKit()) { + BSutils.sendMessage(player, "Kits cannot be sold"); + toSell[i - st] = null; + } else if (toSell[i - st].isEntity()) { + BSutils.sendMessage(player, "Entities cannot be sold"); + toSell[i - st] = null; + } else { + try { + if (!BetterShop.getShop((Player) player).pricelist.isForSale(toSell[i - st])) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("donotwant"). + replace("", toSell[i - st].coloredName())); + toSell[i - st] = null; + } + } catch (Exception e) { + throw new WrappedCommandException(e); + } + } + } + } // "[All Sellable]" + }// initial check complete: set as last action + usersellHistory.put(((Player) player).getDisplayName(), "shopsellall " + Str.concatStr(s)); + try { + // now sell the items + sellItems((Player) player, onlyInv, getCanSell((Player) player, onlyInv, toSell, -1)); + } catch (Exception ex) { + throw new WrappedCommandException(ex); + } + } + + @Command(commands = {"shopsellagain", "ssellagain", "sellagain", "ssa"}, + aliases = {"sellagain", "s!", "sell!"}, + desc = "Repeat the last selling action the player did", + usage = "", + permissions = {"BetterShop.user.sell"}) + public static void sellagain(CommandSender sender, String[] s) { + if (BSutils.anonymousCheck(sender)) { + return; + } + String action = usersellHistory.get(((Player) sender).getDisplayName()); + if (action == null) { + BSutils.sendMessage(sender, "You have no recent sell history"); + return; + } + ((Player) sender).performCommand(action); + } + + public static List getCanSell(Player player, boolean onlyInv, JItem[] toSell, double customPrice) throws SQLException, Exception { + List items = ItemStackManip.itemStackSummary( + player.getInventory().getContents(), toSell, onlyInv ? 9 : 0); + Shop shop = BetterShop.getShop(player); + JItem toSell1 = null; + +// ArrayList notwant = new ArrayList(); + if (toSell == null || toSell.length == 0 || (toSell.length == 1 && toSell[0] == null)) { + // null is a wildcard for all + toSell = null; + } else if (toSell != null) { + if (toSell.length == 1) { + toSell1 = toSell[0]; + } + // remove unwanted items + for (int i = 0; i < toSell.length; ++i) { + if (toSell[i] != null + && (!(customPrice >= 0 || shop.pricelist.isForSale(toSell[i])) + || (toSell[i].IsTool() && !BetterShop.getSettings().buybacktools))) { +// notwant.add(toSell[i].coloredName()); + toSell[i] = null; + } + } + } + + // remove unsellable items + if (toSell == null) { + for (int i = 0; i < items.size(); ++i) { + if (!(customPrice >= 0 || shop.pricelist.isForSale(items.get(i)))) { + items.remove(i--); + } else if (!BetterShop.getSettings().buybacktools + && items.get(i).getDurability() > 0) { + // if not buying used tools, check if that is what this is + JItem t = JItemDB.GetItem(items.get(i)); + if (t.IsTool()) { + items.remove(i--); + } + } + } + } else { + for (int i = 0; i < items.size(); ++i) { + // if not trying to sell, or item is not for sale, remove + if (!contains(toSell, items.get(i)) + || !(customPrice >= 0 || shop.pricelist.isForSale(items.get(i)))) { + items.remove(i--); + } + } + } + + // now do a scan for max. sellable + boolean overstock = false; + if (shop.config.useStock() && shop.config.noOverStock) { + for (int i = 0; i < items.size(); ++i) { + // check if avaliable stock + JItem it = JItemDB.GetItem(items.get(i)); + long free = shop.stock.freeStockRemaining(it); + if (free == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("maxstock"). + replace("", it.coloredName())); + items.remove(i--); + overstock = true; + } else if (free > 0 && items.get(i).getAmount() > free) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("highstock"). + replace("", it.coloredName()). + replace("", String.valueOf(free))); + items.get(i).setAmount((int) free); + } + } + } + + if (items.isEmpty() && !overstock) { + BSutils.sendMessage(player, "You Don't have any " + + (toSell == null ? "Sellable Items" + : (toSell.length == 1 ? toSell1.coloredName() : /*Str.concatStr(toSell, ",")*/ "of those items"))); + } +// if (notwant.size() > 0) { +// BSutils.sendMessage(player, BetterShop.getConfig().getString("donotwant"). +// replace("", notwant.size() > 1 +// ? "(" + Str.concatStr(notwant.toArray(new String[0]), ", ") + ")" +// : notwant.get(0))); +// } + return items; + } + + public static double sellItems(Player player, boolean onlyInv, JItem item, int amt) throws SQLException, Exception { + return sellItems(player, onlyInv, new JItem[]{item}, amt, -1); + } + + public static double sellItems(Player player, boolean onlyInv, JItem item, int amt, double customPrice) throws SQLException, Exception { + return sellItems(player, onlyInv, new JItem[]{item}, amt, customPrice); + } + + public static double sellItems(Player player, boolean onlyInv, JItem[] items, int amt) throws SQLException, Exception { + return sellItems(player, onlyInv, items, amt, -1); + } + + public static double sellItems(Player player, boolean onlyInv, JItem[] items, int amt, double customPrice) throws SQLException, Exception { + List sellable = getCanSell((Player) player, onlyInv, items, customPrice); + // should be at least one item + if (!sellable.isEmpty()) { + if (amt > 0) { + int amtLeft[] = new int[items.length], + totalLeft = 0; + for (int i = 0; i < items.length; ++i) { + amtLeft[i] = amt; + } + int amtHas = 0, i = 0; + String itemN = ""; + for (JItem it : items) { + if (it != null) { + itemN += it.coloredName() + ", "; + for (ItemStack is : sellable) { + if (it.equals(is)) { + totalLeft += amt; + amtHas += is.getAmount(); + if (is.getAmount() > amtLeft[i]) { + is.setAmount(amtLeft[i]); + amtLeft[i] = 0; + break; + } else { + amtLeft[i] -= is.getAmount(); + } + } + } + } + ++i; + } + if (itemN.length() > 0) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + if (totalLeft - amtHas > 0) { // not enough to sell + BSutils.sendMessage(player, + BetterShop.getSettings().getString("donthave"). + replace("", String.valueOf(amtHas)). + replace("", String.valueOf(totalLeft)). + replace("", itemN)); + } + } + return sellItems(player, onlyInv, sellable, customPrice); + } + return 0; + } + + public static double sellItems(Player player, boolean onlyInv, List sellable) throws SQLException, Exception { + return sellItems(player, onlyInv, sellable, -1); + } + + public static double sellItems(Player player, boolean onlyInv, List sellable, double customPrice) throws SQLException, Exception { + + if (sellable == null) { + sellable = getCanSell((Player) player, onlyInv, null, customPrice); + } + if (sellable.isEmpty()) { + return 0; + } + + Shop shop = BetterShop.getShop(player); + + // list of transactions made + List transactions = new LinkedList(); + + ItemStack[] inv = player.getInventory().getContents(); + + double credit = 0; // total to pay player + int amtSold = 0; //total items sold + + // name of item(s) sold + String itemN = ""; + + for (ItemStack it : sellable) { + int amtLeft = it.getAmount(); + JItem selling = JItemDB.GetItem(it); + double price = 0; + if (selling == null) { + BetterShopLogger.Log(Level.SEVERE, "Unexpected unknown inventory item: " + it); + continue; + } + + for (int i = (onlyInv ? 9 : 0); i < inv.length && amtLeft > 0; ++i) { + if (selling.equals(inv[i])) { + + int amt = inv[i].getAmount(); + if (amtLeft < amt) { + inv[i].setAmount(amt - amtLeft); + amt = amtLeft; + } else { + inv[i].setAmount(0); + } + + price += selling.IsTool() ? ((customPrice >= 0 ? customPrice * amt : shop.pricelist.itemSellPrice(player, selling, amt)) + * (1 - ((double) inv[i].getDurability() / selling.MaxDamage()))) + : (customPrice >= 0 ? customPrice * amt : shop.pricelist.itemSellPrice(player, selling, amt)); + + if (inv[i].getAmount() <= 0) { + inv[i] = null; + } + amtSold += amt; + amtLeft -= amt; + } + } + if (amtLeft > 0) { + BetterShopLogger.Severe("Not all Items Sold: " + amtLeft + " " + selling.Name() + " left.."); + } + int numSold = it.getAmount() - amtLeft; + itemN += selling.coloredName() + ", "; + if (shop.config.useStock()) { + shop.stock.changeItemAmount(selling, numSold); + } + transactions.add(new UserTransaction(selling, true, numSold, + numSold > 0 ? price / numSold : price, player.getDisplayName())); + credit += price; + } + + player.getInventory().setContents(inv); + BSEcon.credit(player, credit); + + if (itemN.length() > 1) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + + BSutils.sendFormttedMessage(player, "sellmsg", itemN, amtSold, credit); + + // last step: log transactions + if (BetterShop.getSettings().logUserTransactions) { + try { + for (UserTransaction t : transactions) { + shop.transactions.addRecord(t); + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } + + return credit; + } + + private static boolean contains(JItem[] items, ItemStack it) { + if (items != null) { + for (JItem i : items) { + if (i != null && i.equals(it)) { + return true; + } + } + } + return false; + } +} // end class SellCommands + diff --git a/src/me/jascotty2/bettershop/commands/ShopCommand.java b/src/me/jascotty2/bettershop/commands/ShopCommand.java new file mode 100644 index 0000000..bdd00ac --- /dev/null +++ b/src/me/jascotty2/bettershop/commands/ShopCommand.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: intermediary command for others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.commands; + +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.spout.SpoutPopupDisplay; +import me.jascotty2.lib.bukkit.commands.Command; +import me.jascotty2.lib.bukkit.commands.NestedCommand; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.getspout.spoutapi.player.SpoutPlayer; + +/** + * @author jacob + */ +public class ShopCommand { + + @Command(commands = {"shop", "bettershop", "bshop"}, + desc = "General shop commands") + @NestedCommand({HelpCommands.class, AdminCommands.class, ListCommands.class, + BuyCommands.class, SellCommands.class, + RegionCommands.class, ChestCommands.class, Commands.class}) + public static void shop(CommandSender player, String[] s) { + } + + public static class Commands { + + @Command(commands = {}, + aliases = {"gui", "g", "menu"}, + desc = "Open the Spout GUI, if possible", + usage = "", + permissions = {"BetterShop.user.spout"}) + public static void openGui(CommandSender player, String[] s) { + if (BSutils.anonymousCheck(player)) { + return; + } + if (!BetterShop.spoutEnabled()) { + BSutils.sendMessage(player, ChatColor.RED + "Spout is not installed"); + return; + } + SpoutPlayer sp = (SpoutPlayer) player; + if(!sp.isSpoutCraftEnabled()){ + BSutils.sendMessage(player, ChatColor.RED + "You don't have SpoutCraft!"); + } else { + SpoutPopupDisplay.popup(sp); + } + } + } +} // end class ShopCommand + diff --git a/src/me/jascotty2/bettershop/enums/BetterShopPermission.java b/src/me/jascotty2/bettershop/enums/BetterShopPermission.java new file mode 100644 index 0000000..ef7b228 --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/BetterShopPermission.java @@ -0,0 +1,108 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: permissions nodes used in the plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.enums; + +public enum BetterShopPermission { + + /** + * generic user permissions + */ + USER("BetterShop.user"), + /** + * look through shop listing of prices + */ + USER_LIST("BetterShop.user.list"), + /** + * check the price of item(s) + */ + USER_CHECK("BetterShop.user.check"), + /** + * view ingame help menu + */ + USER_HELP("BetterShop.user.help"), + /** + * buy items from the shop + */ + USER_BUY("BetterShop.user.buy"), + /** + * sell items to the shop + */ + USER_SELL("BetterShop.user.sell"), + /** + * allow a user to use the spout gui menu + */ + USER_SPOUT("BetterShop.user.spout"), + /** + * allow a user to use the a chest shop + */ + USER_CHEST("BetterShop.user.chest"), + /** + * generic admin permissions + */ + ADMIN("BetterShop.admin"), + /** + * add/edit items to/in the shop + */ + ADMIN_ADD("BetterShop.admin.add"), + /** + * remove items from the shop + */ + ADMIN_REMOVE("BetterShop.admin.remove"), + /** + * reload configuration & pricelist + */ + ADMIN_LOAD("BetterShop.admin.load"), + /** + * show shop stats + */ + ADMIN_INFO("BetterShop.admin.info"), + /** + * gives the ability to purchase 'illegal' items + */ + ADMIN_ILLEGAL("BetterShop.admin.illegal"), + /** + * backing up and restoring the pricelist + */ + ADMIN_BACKUP("BetterShop.admin.backup"), + /** + * manually restock (if item stock is enabled) + */ + ADMIN_RESTOCK("BetterShop.admin.restock"), + /** + * ability to add/remove shop signs + */ + ADMIN_MAKESIGN("BetterShop.admin.makesign"), + /** + * ability to add/remove shop regions + */ + ADMIN_REGION("BetterShop.admin.region"), + /** + * ability to add/remove shop chests + */ + ADMIN_CHESTS("BetterShop.admin.chests"); + public final String permissionNode; + + BetterShopPermission(String per) { + permissionNode = per; + } + + @Override + final public String toString() { + return permissionNode; + } +} diff --git a/src/me/jascotty2/bettershop/enums/CommandShopMode.java b/src/me/jascotty2/bettershop/enums/CommandShopMode.java new file mode 100644 index 0000000..27a361a --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/CommandShopMode.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: setting for how the command shop is used + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.enums; + +public enum CommandShopMode { + + GLOBAL, REGIONS, BOTH, NONE +} // end class CommandShopMode diff --git a/src/me/jascotty2/bettershop/enums/DBType.java b/src/me/jascotty2/bettershop/enums/DBType.java new file mode 100644 index 0000000..78f19de --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/DBType.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: type of database the plugin should use + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.enums; + +public enum DBType { + + FLATFILE, MYSQL, //SQLITE +} diff --git a/src/me/jascotty2/bettershop/enums/DiscountMethod.java b/src/me/jascotty2/bettershop/enums/DiscountMethod.java new file mode 100644 index 0000000..36448e8 --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/DiscountMethod.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: how discounts are applied when selling items + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.enums; + +public enum DiscountMethod { + LOWER, NONE, HIGHER +} diff --git a/src/me/jascotty2/bettershop/enums/EconMethod.java b/src/me/jascotty2/bettershop/enums/EconMethod.java new file mode 100644 index 0000000..68f5090 --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/EconMethod.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: how economy is calculated + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.enums; + +public enum EconMethod { + AUTO, EXP, TOTAL, BULTIN; +} diff --git a/src/me/jascotty2/bettershop/enums/ShopMethod.java b/src/me/jascotty2/bettershop/enums/ShopMethod.java new file mode 100644 index 0000000..e8b63c5 --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/ShopMethod.java @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2012 Jacob Scott + * Description: how the player is using the shop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.enums; + +public enum ShopMethod { + COMMAND, SIGN, CHEST, SPOUT, PLUGIN +} diff --git a/src/me/jascotty2/bettershop/enums/SpoutCategoryMethod.java b/src/me/jascotty2/bettershop/enums/SpoutCategoryMethod.java new file mode 100644 index 0000000..454d6e7 --- /dev/null +++ b/src/me/jascotty2/bettershop/enums/SpoutCategoryMethod.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: which category options are avaliable + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.enums; + +public enum SpoutCategoryMethod { + + NONE, TABBED, CYCLE +} diff --git a/src/me/jascotty2/bettershop/regionshops/BSRegions.java b/src/me/jascotty2/bettershop/regionshops/BSRegions.java new file mode 100644 index 0000000..ab4fb3d --- /dev/null +++ b/src/me/jascotty2/bettershop/regionshops/BSRegions.java @@ -0,0 +1,242 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for managing & tracking regions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.regionshops; + +import com.sk89q.wg_regions_52.ApplicableRegionSet; +import com.sk89q.wg_regions_52.CuboidRegion; +import com.sk89q.wg_regions_52.PolygonalRegion; +import com.sk89q.wg_regions_52.Region; +import com.sk89q.wg_regions_52.managers.RegionManager; +import com.sk89q.wg_regions_52.managers.GlobalRegionManager; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.selections.CuboidSelection; +import com.sk89q.worldedit.bukkit.selections.Polygonal2DSelection; +import com.sk89q.worldedit.bukkit.selections.Selection; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import me.jascotty2.bettershop.BSutils; +import org.bukkit.Server; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * @author jacob + */ +public class BSRegions { + + protected static WorldEditPlugin worldEdit = null; + File loadedFolder = null; + protected final GlobalRegionManager globalRegionManager; + + public BSRegions(Server server, File dataFolder) { + loadedFolder = dataFolder; + globalRegionManager = new GlobalRegionManager(server, dataFolder); + } // end default constructor + + public void load() { + globalRegionManager.preload(); + } + + public boolean hasRegion(Location loc) { + return globalRegionManager.hasRegion(loc); + } + + public String getRegionName(Location loc) { + if (loc != null) { + final World world = loc.getWorld(); + final RegionManager mgr = globalRegionManager.get(world); + ApplicableRegionSet regions = mgr.getApplicableRegions(loc); + if (regions.size() > 0) { + Region r = regions.iterator().next(); + return r.getId(); + } + } + return null; + } + + public boolean define(Player pl, String name) { + return worldEdit == null ? false : define(pl, name, worldEdit.getSelection(pl)); + } + + public boolean define(Player pl, String id, Selection sel) { + if (sel == null) { + BSutils.sendMessage(pl, ChatColor.YELLOW + "Select a region first"); + return false; + } else if (!Region.isValidId(id)) { + BSutils.sendMessage(pl, ChatColor.RED + "Invalid region ID specified!"); + return false; + } else if (id.equalsIgnoreCase("__global__")) { + BSutils.sendMessage(pl, ChatColor.RED + "A region cannot be named __global__"); + return false; + } + + Region region; + + // Detect the type of region from WorldEdit + if (sel instanceof Polygonal2DSelection) { + final Polygonal2DSelection polySel = (Polygonal2DSelection) sel; + final int minY = polySel.getNativeMinimumPoint().getBlockY(); + final int maxY = polySel.getNativeMaximumPoint().getBlockY(); + region = new PolygonalRegion(id, polySel.getNativePoints(), minY, maxY); + } else if (sel instanceof CuboidSelection) { + final BlockVector min = sel.getNativeMinimumPoint().toBlockVector(); + final BlockVector max = sel.getNativeMaximumPoint().toBlockVector(); + region = new CuboidRegion(id, min, max); + } else { + BSutils.sendMessage(pl, ChatColor.RED + "The type of region selected in WorldEdit is unsupported!"); + return false; + } + + final RegionManager mgr = globalRegionManager.get(sel.getWorld()); + mgr.addRegion(region); + + try { + mgr.save(); + BSutils.sendMessage(pl, ChatColor.GREEN + "Region saved as " + id + "."); + } catch (IOException e) { + BSutils.sendMessage(pl, ChatColor.RED + "Failed to write regions file: " + e.getMessage()); + } + return true; + } + + public String[] list(final World world, int pageSize, int pageNum) { + String[] regionIDList = list(world); + int pages = (int) Math.ceil(regionIDList.length / (float) pageSize); + if (pageNum < pages) { + int numLeft = regionIDList.length - pageNum * pageSize; + String[] page = new String[numLeft > pageSize ? pageSize : numLeft]; + System.arraycopy(regionIDList, pageNum * pageSize, page, 0, page.length); + return page; + } + return new String[0]; + } + + public String[] list(final World world) { + if (world != null) { + + final RegionManager mgr = globalRegionManager.get(world); + final Map regions = mgr.getRegions(); + + final String[] regionIDList = regions.keySet().toArray(new String[0]); + Arrays.sort(regionIDList); + return regionIDList; + } else { + + final Map> regions = new TreeMap>(); + + for (Entry mgr : globalRegionManager.getAllEntries()) { + regions.put(mgr.getKey(), mgr.getValue().getRegions()); + } + + int size = 0;//regions.size(); + for (final String w : regions.keySet()) { + size += regions.get(w).size(); + } + + int i = 0; + String[] regionIDList = new String[size]; + for (final String w : regions.keySet()) { + for (final String r : regions.get(w).keySet()) { + regionIDList[i++] = w + ":" + r; + } + } + Arrays.sort(regionIDList); + + return regionIDList; + } + } + + public void remove(World w, String id) { + remove(null, w, id); + } + + public boolean remove(CommandSender pl, World world, String id) { + if (world == null) { + if (pl == null) { + return false; + } else if (id.contains(":") || !(pl instanceof Player)) { + String worldname; + if (id.contains(":") && id.indexOf(':') == id.lastIndexOf(':')) { + worldname = id.substring(0, id.indexOf(':')); + id = id.substring(id.indexOf(':') + 1); + } else { + BSutils.sendMessage(pl, ChatColor.RED + "must specify world (world:region)"); + return false; + } + + for (final World w : pl.getServer().getWorlds()) { + if (w.getName().equalsIgnoreCase(worldname)) { + world = w; + break; + } + } + + if (world == null) { + BSutils.sendMessage(pl, ChatColor.RED + "world not found"); + return false; + } + } else if (pl instanceof Player) { + world = ((Player) pl).getWorld(); + } + } + if (world != null) { + final RegionManager mgr = globalRegionManager.get(world); + final Region region = mgr.getRegion(id); + + if (region == null) { + BSutils.sendMessage(pl, ChatColor.RED + "Could not find a region by that ID."); + return false; + } + + mgr.removeRegion(id); + + BSutils.sendMessage(pl, ChatColor.GREEN + "Region '" + id + "' removed."); + + try { + mgr.save(); + } catch (IOException e) { + BSutils.sendMessage(pl, ChatColor.RED + "Failed to write regions file: " + e.getMessage()); + } + } + return true; + } + + public Collection getAll() { + ArrayList all = new ArrayList(); + for (RegionManager r : globalRegionManager.getAll()) { + all.addAll(r.getRegions().values()); + } + return all; + } + + public Set> getAllRegionManagers() { + return globalRegionManager.getAllEntries(); + } +} // end class BSRegions + diff --git a/src/me/jascotty2/bettershop/regionshops/RegionShopManager.java b/src/me/jascotty2/bettershop/regionshops/RegionShopManager.java new file mode 100644 index 0000000..35f1728 --- /dev/null +++ b/src/me/jascotty2/bettershop/regionshops/RegionShopManager.java @@ -0,0 +1,207 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: shop manager with support for different region-based shops + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.regionshops; + +import com.sk89q.wg_regions_52.Region; +import com.sk89q.wg_regions_52.managers.RegionManager; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * @author jacob + */ +public class RegionShopManager { + + BSRegions regions = null; + Map shops = new HashMap(); + + public int load() { + if (regions == null) { + Plugin p = BetterShop.getPlugin().getServer().getPluginManager().getPlugin("WorldEdit"); + try { + regions = new BSRegions(BetterShop.getPlugin().getServer(), + BetterShop.getPlugin().getDataFolder()); + regions.load(); + if (p != null && p instanceof WorldEditPlugin) { + BSRegions.worldEdit = (WorldEditPlugin) p; + } + } catch (NoClassDefFoundError e) { + if (p == null) { + BetterShopLogger.Log("to enable existing regions, put a copy of WorldEdit in the lib folder, " + + "or install WorldEdit to the server"); + } else { + BetterShopLogger.Warning("Unexpected error while loading region manager"); + } + regions = null; + } + } + + int numErrors = 0; + // TODO: allow multiple shop definitions.. + { + Shop s = new Shop(); + if (!s.load(null)) { + BetterShopLogger.Warning("Error Loading " + (true ? "Main" : null) + " Shop"); + ++numErrors; + } + shops.put(null, s); + } + if (regions != null) { + for (Entry m : regions.getAllRegionManagers()) { + for (Region r : m.getValue().getRegions().values()) {//regions.getAll()) {// + if (!shops.containsKey(r.getInfo())) { + BetterShopLogger.Warning("invalid shop defined in region " + r.getId() + " (removed)"); + regions.remove(null, null, m.getKey() + ":" + r.getId()); + } else { + shops.put(r.getId(), shops.get(r.getInfo())); + } + } + } + } + return numErrors; + } + + public boolean isCommandShopEnabled(Location loc) { + if (loc == null) { + return BetterShop.getSettings().useCommandShop(); + } else if (!BetterShop.getSettings().useCommandShop()) { + return false; + } else if (BetterShop.getSettings().useCommandShopGlobal()) { + return true; + } + boolean isRegion = inShopRegion(loc); + return BetterShop.getSettings().useRegionCommandShop() && isRegion + || BetterShop.getSettings().useGlobalCommandShop() && !isRegion; + } + + /** + * Primarily for admin commands, if a shop exists in this area + * @param loc + * @return + */ + public boolean locationHasShop(Location loc) { + if (BetterShop.getSettings().useCommandShopGlobal()) { + return true; + } else if (BetterShop.getSettings().useRegionCommandShop()) { + return inShopRegion(loc); + } else { + return !inShopRegion(loc); + } + } + + public boolean inShopRegion(Location loc) { + return regions == null ? false : regions.hasRegion(loc); + } + + public Shop getShop(Location loc) { + if (regions == null) { + return shops.get(null); + } else { + return shops.get(regions.getRegionName(loc)); + } + } + + public Shop getShop(String s) { + return shops.get(s); + } + + public Collection getShops() { + return shops.values(); + } + + public boolean canUseRegions() { + return regions != null; + } + + public boolean addRegion(String regionName, Player player) { + return addRegion(regionName, null, player); + } + + public boolean addRegion(String regionName, String shopName, Player player) { + if (regions != null && shops.containsKey(shopName)) { + if (regions.define(player, regionName)) { + shops.put(regionName, shops.get(shopName)); + return true; + } + } + return false; + } + + public boolean removeRegion(CommandSender sender, String regionName) { + if (regions == null) { + return false; + } + return regions.remove(sender, + sender instanceof Player ? ((Player) sender).getWorld() : null, regionName); + } + + public String[] getRegionList(World w, int page, int pazesize) { + return regions == null ? new String[0] : regions.list(w, pazesize, page); + } + + public int numRegions() { + return regions == null ? 0 : regions.getAll().size(); + } + + public void checkRestock() { + for (Shop s : shops.values()) { + if (s.stock != null && s.config.useStock()) { + s.stock.checkStockRestock(); + } + } + } + + public void restock() { + for (Shop s : shops.values()) { + if (s.stock != null && s.config.useStock()) { + s.stock.restock(true); + } + } + } + + public void closeAll() { + for (Shop s : shops.values()) { + try { + s.stock.close(); + } catch (Exception ex) { + //BetterShopLogger.Severe(ex, false); + } + try { + s.pricelist.close(); + } catch (Exception ex) { + //BetterShopLogger.Severe(ex, false); + } + } + shops.clear(); + if (regions != null) { + regions.globalRegionManager.unloadAll(); + } + } +} // end class RegionShopManager + diff --git a/src/me/jascotty2/bettershop/shop/BSItemStock.java b/src/me/jascotty2/bettershop/shop/BSItemStock.java new file mode 100644 index 0000000..05fb211 --- /dev/null +++ b/src/me/jascotty2/bettershop/shop/BSItemStock.java @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: provides options for items to have stock + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.shop; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.shop.ItemStock; +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.lib.mysql.MySQLItemStock; +import java.io.File; +import java.sql.SQLException; +import java.util.Date; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.item.JItemDB; +import org.bukkit.inventory.ItemStack; + +public class BSItemStock extends ItemStock { + + protected Date lastStock; + final Shop shop; + private BSPriceList pricelist; + + public BSItemStock(Shop shop) { + super(); + this.shop = shop; + this.pricelist = shop.pricelist; + } // end default constructor + + public final boolean load() { + lastStock = new Date(); + useCache = BetterShop.getSettings().useDBCache; + dbCacheTTL = BetterShop.getSettings().priceListLifespan; + if (BetterShop.getSettings().useMySQL()) { + databaseType = DBType.MYSQL; + try { + MySQLstockList = new MySQLItemStock( + pricelist.getMySQLconnection(), + pricelist.config.stockTablename); + if (MySQLstockList != null && MySQLstockList.isConnected()) { + return checkMissingStock(); + } + } catch (SQLException ex) { + BetterShopLogger.Log(Level.SEVERE, "Failed to connect to MySQL database connection...", ex); + } + } else { + databaseType = DBType.FLATFILE; + try { + //System.out.println("attempting FlatFile: " + BSConfig.pluginFolder.getPath() + File.separatorChar + BetterShop.getConfig().tableName + ".csv"); + if (loadFile(new File(BSConfig.pluginFolder.getPath() + File.separatorChar + + pricelist.config.stockTablename + ".csv"))) { + BetterShopLogger.Log(Level.INFO, pricelist.config.stockTablename + ".csv loaded."); + return checkMissingStock(); + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + BetterShopLogger.Log(Level.SEVERE, "Failed to load pricelist database " + + pricelist.config.stockTablename + ".csv", false); + } + return false; + } + + /** + * checks for items not in stocklist & gives then the default amount + * @return false if there was an error while adding new items + */ + public boolean checkMissingStock() { + try { + JItem[] prices = pricelist.getItems(); + for (JItem i : prices) { + if (stockList.indexOf(i) == -1) { + setItemAmount(i, pricelist.config.startStock); + } + } + return true; + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + return false; + } + + public void restock(boolean forced) { + if (!forced) { + checkStockRestock(); + } else { + try { + lastStock = new Date(); + JItem[] prices = pricelist.getItems(); + for (JItem i : prices) { + setItemAmount(i, pricelist.config.startStock); + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } + } + + public void checkStockRestock() { + if (pricelist.config.useStock() && pricelist.config.restock > 0 + && ((new Date()).getTime() - lastStock.getTime()) / 1000 > pricelist.config.restock) { + restock(true); + } + } + + public long freeStockRemaining(ItemStack check) { + JItem c = JItemDB.GetItem(check); + return c != null ? freeStockRemaining(c.ID(), (byte) c.Data()) : -1; + } + + public long freeStockRemaining(JItem check) { + return check != null ? freeStockRemaining(check.ID(), (byte) check.Data()) : -1; + } + + public long freeStockRemaining(int id, byte dat) { + if (BetterShop.getSettings().useItemStock) { + try { + long st = getItemAmount(id, dat); + if (st < 0) { + return -1; + } else if (pricelist.config.noOverStock) { + return pricelist.config.maxStock - st; + } else { + return Long.MAX_VALUE - st; + } + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } + return -1; + } + + @Override + public void changeItemAmount(JItem it, long delta) throws SQLException, Exception { + if (it == null || !pricelist.config.useStock() || getItemAmount(it) < 0) { + return; + } + ItemStockEntry itp = getItemEntry(it); + if (itp != null) { + delta += itp.amount; + if (delta >= pricelist.config.maxStock) { + setItemAmount(it, pricelist.config.maxStock); + } else if (delta <= 0) { + setItemAmount(it, 0); + } else { + //System.out.println("new amount: " + delta); + setItemAmount(it, delta);//itp.amount + delta); + } + } + } +} // end class BSItemStock + diff --git a/src/me/jascotty2/bettershop/shop/BSPriceList.java b/src/me/jascotty2/bettershop/shop/BSPriceList.java new file mode 100644 index 0000000..dd0f363 Binary files /dev/null and b/src/me/jascotty2/bettershop/shop/BSPriceList.java differ diff --git a/src/me/jascotty2/bettershop/shop/BSTransactionLog.java b/src/me/jascotty2/bettershop/shop/BSTransactionLog.java new file mode 100644 index 0000000..cf4f875 --- /dev/null +++ b/src/me/jascotty2/bettershop/shop/BSTransactionLog.java @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for maintaining a log of transactions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.shop; + +import me.jascotty2.lib.bukkit.shop.TotalTransaction; +import me.jascotty2.lib.bukkit.shop.TransactionLog; +import java.io.File; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.utils.BetterShopLogger; + +public class BSTransactionLog extends TransactionLog { + + final Shop shop; + private BSPriceList pricelist; + + public BSTransactionLog(Shop shop) { + super(); + this.shop = shop; + this.pricelist = shop.pricelist; + } // end default constructor + + public final boolean load() { + transactions.clear(); + totalTransactions.clear(); + logUserTransactions = BetterShop.getSettings().logUserTransactions; + logTotalTransactions = BetterShop.getSettings().logTotalTransactions; + transLogTablename = pricelist.config.transLogTablename; + recordTablename = pricelist.config.recordTablename; + userTansactionLifespan = BetterShop.getSettings().userTansactionLifespan; + + if (BetterShop.getSettings().useMySQL()) { + // use same connection pricelist is using (pricelist MUST be initialized.. does not check) + MySQLconnection = pricelist.getMySQLconnection(); + if (MySQLconnection == null) { + return isLoaded = logTotalTransactions = logUserTransactions = false; + } else { + try { + if (logUserTransactions) { + if (!MySQLconnection.tableExists(transLogTablename)) { + logUserTransactions = createTransactionLogTable(); + } else { + tableCheck(); + try { + truncateRecords(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + } + } + if (logTotalTransactions) { + if (!MySQLconnection.tableExists(recordTablename)) { + logTotalTransactions = createTransactionRecordTable(); + } else { + //load into memory + //for(Result) + ResultSet tb = MySQLconnection.getTable(recordTablename); + for (tb.beforeFirst(); tb.next();) { + totalTransactions.add(new TotalTransaction( + tb.getLong("LAST"), tb.getInt("ID"), tb.getInt("SUB"), + tb.getString("NAME"), tb.getLong("SOLD"), tb.getLong("BOUGHT"))); + } + } + } + } catch (SQLException ex) { + BetterShopLogger.Log(Level.SEVERE, "Error retrieving table list", ex); + return isLoaded = logTotalTransactions = logUserTransactions = false; + } + } + } else { + MySQLconnection = null; + flatFile = new File(BSConfig.pluginFolder.getAbsolutePath() + + File.separatorChar + pricelist.config.transLogTablename + ".csv"); + totalsFlatFile = new File(BSConfig.pluginFolder.getAbsolutePath() + + File.separatorChar + pricelist.config.recordTablename + ".csv"); + } + try { + updateCache(); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + + return isLoaded = true; + } + + public boolean isOpened() { + return isLoaded && (BetterShop.getSettings().useMySQL() + ? MySQLconnection != null && MySQLconnection.isConnected() : flatFile != null); + } + + public String databaseName() { + return BetterShop.getSettings().useMySQL() + ? (MySQLconnection != null ? MySQLconnection.getDatabaseName() : "null") + : (flatFile != null ? flatFile.getName() : "null"); + } + + public void tableCheck() { + if (BetterShop.getSettings().useMySQL() + && MySQLconnection != null && MySQLconnection.isConnected()) { + try { + //Version 1.6.1.1+ ALTER TABLE BetterShopMarketActivity ADD COLUMN PRICE DECIMAL(11,2); + + if (logUserTransactions + && !MySQLconnection.columnExists(transLogTablename, "PRICE")) { + MySQLconnection.runUpdate("ALTER TABLE " + transLogTablename + " ADD COLUMN PRICE DECIMAL(11,2);"); + BetterShopLogger.Log(transLogTablename + " updated"); + } + } catch (SQLException ex) { + BetterShopLogger.Log(Level.SEVERE, "Error while upgrading MySQL Table", ex); + } + } + } +} // end class BSLog + diff --git a/src/me/jascotty2/bettershop/shop/Shop.java b/src/me/jascotty2/bettershop/shop/Shop.java new file mode 100644 index 0000000..084367a --- /dev/null +++ b/src/me/jascotty2/bettershop/shop/Shop.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: a generic shop class, including pricelist, stock, and transaction log + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.shop; + +/** + * @author jacob + */ +public class Shop { + + public final BSPriceList pricelist; + public final BSItemStock stock; + public final BSTransactionLog transactions; + public final ShopConfig config = new ShopConfig(); + protected String name; + + public Shop() { + pricelist = new BSPriceList(this); + stock = new BSItemStock(this); + transactions = new BSTransactionLog(this); + } // end default constructor + + public boolean load(String shopName){ + this.name = shopName == null ? "Main" : shopName; + if(shopName == null){ + config.set(null); + } else { + // TODO: expand for possible multiple shops + } + if(pricelist.load()){ + boolean l1 = stock.load(); + boolean l2 = transactions.load(); + return l1 && l2; + } + return false; + } + + public String getName() { + return name; + } + +} // end class Shop diff --git a/src/me/jascotty2/bettershop/shop/ShopConfig.java b/src/me/jascotty2/bettershop/shop/ShopConfig.java new file mode 100644 index 0000000..ce12ee7 --- /dev/null +++ b/src/me/jascotty2/bettershop/shop/ShopConfig.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: configuration settings for an individual shop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.shop; + +import java.util.List; +import me.jascotty2.bettershop.BetterShop; + +/** + * @author jacob + */ +public class ShopConfig { + // general + + public String sql_username = "root", + sql_password = "root", + sql_database = "minecraft", + sql_hostName = "localhost", + sql_portNum = "3306"; + // pricelist + public String tableName = "BetterShop"; + // transaction logging + public String transLogTablename = "BetterShopMarketActivity", + recordTablename = "BetterShopTransactionTotals"; + // stock settings + public String stockTablename = "BetterShopItemStock"; + // how much an added item has to start with + public long startStock = 200; + // max stock to carry (stock is increased with sales) + public long maxStock = 500; + // deny sales if stock is full? + public boolean noOverStock = true; + // restock interval.. automatic, and stock will be reset to startStock value + public long restock = 21600; //6h + + public void set(ShopConfig copy) { + if (copy == null) { + set(BetterShop.getSettings().mainShopConfig); + } else { + sql_username = copy.sql_username; + sql_password = copy.sql_password; + sql_database = copy.sql_database; + sql_hostName = copy.sql_hostName; + sql_portNum = copy.sql_portNum; + + tableName = copy.tableName; + + transLogTablename = copy.transLogTablename; + recordTablename = copy.recordTablename; + + stockTablename = copy.stockTablename; + startStock = copy.startStock; + maxStock = copy.maxStock; + noOverStock = copy.noOverStock; + restock = copy.restock; + } + } + + public List getCustomSort() { + return BetterShop.getSettings().sortOrder; + } + + public boolean useDBcaching() { + return BetterShop.getSettings().useDBCache; + } + + public int dbCacheTime() { + return BetterShop.getSettings().tempCacheTTL; + } + + public long pricelistCacheTime() { + return BetterShop.getSettings().priceListLifespan; + } + + public boolean useMySQL() { + return BetterShop.getSettings().useMySQL(); + } + + public boolean useStock() { + return BetterShop.getSettings().useItemStock; + } + + public int getPageSize() { + return BetterShop.getSettings().pagesize; + } + + public String getListFormat() { + return BetterShop.getSettings().getString("listing"); + } + + public String getListHead() { + return BetterShop.getSettings().getString("listhead"); + } + + public String getListTail() { + return BetterShop.getSettings().getString("listtail"); + } + + public boolean allowIllegalPurchase() { + return BetterShop.getSettings().allowbuyillegal; + } +} // end class ShopConfig + diff --git a/src/me/jascotty2/bettershop/signshop/BSSignShop.java b/src/me/jascotty2/bettershop/signshop/BSSignShop.java new file mode 100644 index 0000000..9146130 --- /dev/null +++ b/src/me/jascotty2/bettershop/signshop/BSSignShop.java @@ -0,0 +1,369 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for adding a sign interface to bettershop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.signshop; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.commands.BuyCommands; +import me.jascotty2.bettershop.commands.SellCommands; + +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.lib.bukkit.inventory.ItemStackManip; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +/** + * @author jacob + */ +public class BSSignShop implements Listener { + + final static long signResWait = 5000; + final BetterShop plugin; + final SignDB signsd; + final HashMap playerInteractTime = new HashMap(); + public final SignRestore checkSigns; + + public BSSignShop(BetterShop shop) { + plugin = shop; + signsd = new SignDB(plugin.getServer()); + checkSigns = new SignRestore(plugin, signsd); + + } // end default constructor + + public void registerEvents() { + Plugin bs = BetterShop.getPlugin(); + PluginManager pm = bs.getServer().getPluginManager(); + + pm.registerEvents(this, bs); + pm.registerEvents(checkSigns, bs); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.isCancelled() || !BetterShop.getSettings().signShopEnabled) { + return; + } + + if (event.getClickedBlock() != null + && (event.getClickedBlock().getType() == Material.WALL_SIGN + || event.getClickedBlock().getType() == Material.SIGN_POST)) { + Sign clickedSign = (Sign) event.getClickedBlock().getState(); + Player player = event.getPlayer(); + if (ChatColor.stripColor(clickedSign.getLine(0)).equalsIgnoreCase(ShopSign.SIGN_TEXT)) { + BetterShop.setLastCommand("Sign: [" + clickedSign.getLine(1) + "][" + clickedSign.getLine(2) + "][" + clickedSign.getLine(3) + "]"); + try { + event.setCancelled(event.getAction() == Action.RIGHT_CLICK_BLOCK); + // sign click flood protect + Long lt = playerInteractTime.get(player); + if (lt != null && System.currentTimeMillis() - lt < BSConfig.signInteractWait) { + return; + } + playerInteractTime.put(player, System.currentTimeMillis()); + // general sign info + Shop shop = BetterShop.getShop(event.getClickedBlock().getLocation()); + ShopSign signInfo = signsd.getSignShop(event.getClickedBlock().getLocation()); + boolean run = event.getAction() == Action.RIGHT_CLICK_BLOCK; + if (signInfo != null) { + // pricecheck + String itemN = signInfo.getItem() != null ? signInfo.getItem().coloredName() : ""; + try { + int numCheck = 0; + double total = 0; + if (signInfo.isBuy) { + if (signInfo.item != null) { + if (run) { +// if (signInfo.amount > 0) {// not all + BuyCommands.buyItem(player, signInfo.item, signInfo.amount, signInfo.getCustomPrice()); +// } else { +// BuyCommands.buyAllItem(player, signInfo.item, signInfo.getCustomPrice()); +// } + player.updateInventory(); // may be depricated, but only thing i can get to work :( + return; + } + if (signInfo.getCustomPrice() >= 0) { + int canHold = BSutils.amtCanHold(player, signInfo.item); + int canAfford = 0; + double bal = BSEcon.getBalance(player); + long stock = -1; + try { + stock = shop.stock.getItemAmount(signInfo.item); + } catch (Exception ex) { + BetterShopLogger.Severe("Error in Stock Database", ex); + } + + long amt = signInfo.getCustomPrice() == 0 ? Long.MAX_VALUE + : (long) (bal / signInfo.getCustomPrice()); + if (amt > stock) { + amt = stock; + } + if (amt > Integer.MAX_VALUE) { + canAfford = Integer.MAX_VALUE; + } else { + canAfford = (int) amt; + } + numCheck = canAfford > canHold ? canHold : canAfford; + } else { + numCheck = shop.pricelist.getAmountCanBuy(player, signInfo.item); + } + if (signInfo.amount > 0) { // not all + if (numCheck > signInfo.amount) { + numCheck = signInfo.amount; + } + } + if (numCheck == 0 && shop.config.useStock() && shop.stock.getItemAmount(signInfo.item) == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofstock"). + replace("", signInfo.item.coloredName())); + return; + } + total = signInfo.getCustomPrice() >= 0 ? signInfo.getCustomPrice() * numCheck + : shop.pricelist.itemBuyPrice(player, signInfo.item, numCheck); + } else { + // category + List buy = BuyCommands.getCanBuy(player, signInfo.catItems, signInfo.getCustomPrice()); + if (buy.isEmpty() && !run) { + // can't afford, so display full price + for (JItem it : signInfo.catItems) { + if (it != null && shop.pricelist.canBuy(it)) { + long avail = shop.config.useStock() ? shop.stock.freeStockRemaining(it) : -1; + if (avail != 0) { + buy.add(new ItemStockEntry(it, signInfo.amount > avail && avail > 0 ? avail : signInfo.amount)); + } + } + } + } + JItem[] toBuy = new JItem[buy.size()]; + int n = 0; + for (ItemStockEntry it : buy) { + if (signInfo.amount > 0) { // not all + if (it.amount > signInfo.amount) { + it.amount = signInfo.amount; + } + } + JItem j = JItemDB.GetItem(it.itemNum, (byte) it.itemSub); + toBuy[n++] = j; + itemN += j.coloredName() + ", "; + if (run) { + //BuyCommands.buyItem(player, JItemDB.GetItem(it.itemNum, (byte) it.itemSub), (int) it.amount, signInfo.getCustomPrice()); + } else { + numCheck += it.amount; + total += signInfo.getCustomPrice() >= 0 ? signInfo.getCustomPrice() * it.amount + : shop.pricelist.itemBuyPrice(player, it.itemNum, (byte) it.itemSub, (int) it.amount); + } + } + if (run) { + //BuyCommands.buyItem(player, toBuy, signInfo.amount, signInfo.getCustomPrice()); + BuyCommands.buyItem(player, buy, signInfo.getCustomPrice()); + player.updateInventory(); // may be depricated, but only thing i can get to work :( + return; + } + if (itemN.length() > 0) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + } + } else { + // selling + if (signInfo.item != null) { + numCheck = ItemStackManip.count(event.getPlayer().getInventory().getContents(), signInfo.item); + if (signInfo.amount > 0 && numCheck > signInfo.amount) { + numCheck = signInfo.amount; + } + if (run) { + SellCommands.sellItems(player, signInfo.isInv, signInfo.item, numCheck, signInfo.getCustomPrice()); + player.updateInventory(); // may be depricated, but only thing i can get to work :( + return; + } + total = signInfo.getCustomPrice() >= 0 ? signInfo.getCustomPrice() * numCheck + : shop.pricelist.itemSellPrice(player, signInfo.item, numCheck); + } else { + if (run) { + if (signInfo.catItems != null) { + SellCommands.sellItems(player, signInfo.isInv, + signInfo.catItems, signInfo.amount, signInfo.getCustomPrice()); + } else if (signInfo.inHand) { + ItemStack hand = player.getItemInHand(); + if (hand == null || hand.getAmount() == 0) { + BSutils.sendMessage(event.getPlayer(), "you don't have anything in your hand"); + return; + } + SellCommands.sellItems(player, signInfo.isInv, + JItemDB.GetItem(hand), -1, signInfo.getCustomPrice()); + } else { + SellCommands.sellItems(player, signInfo.isInv, + null, signInfo.getCustomPrice()); + } + player.updateInventory(); // may be depricated, but only thing i can get to work :( + return; + } else { + List sellable; + if (signInfo.catItems != null) { + sellable = SellCommands.getCanSell(player, signInfo.isInv, signInfo.catItems, signInfo.customPrice); + } else if (signInfo.inHand) { + ItemStack hand = player.getItemInHand(); + if (hand == null || hand.getAmount() == 0) { + BSutils.sendMessage(event.getPlayer(), "you don't have anything in your hand"); + return; + } + sellable = SellCommands.getCanSell(player, signInfo.isInv, new JItem[]{JItemDB.GetItem(hand)}, signInfo.customPrice); + } else { + sellable = SellCommands.getCanSell(player, signInfo.isInv, null, signInfo.customPrice); + } + for (ItemStack ite : sellable) { + if (signInfo.amount > 0) { // not all + if (ite.getAmount() > signInfo.amount) { + ite.setAmount(signInfo.amount); + } + } + numCheck += ite.getAmount(); + JItem it = JItemDB.GetItem(ite); + itemN += it.coloredName() + ", "; + total += signInfo.getCustomPrice() >= 0 ? signInfo.getCustomPrice() * ite.getAmount() + : shop.pricelist.itemSellPrice(player, it.ID(), (byte) it.Data(), ite.getAmount()); + } + } + } + } + if (itemN.endsWith(", ")) { + itemN = itemN.substring(0, itemN.length() - 2); + if (itemN.contains(",")) { + itemN = "(" + itemN + ")"; + } + } + BSutils.sendMessage(event.getPlayer(), + BetterShop.getSettings().getString(signInfo.isBuy ? "multipricecheckbuy" : "multipricechecksell"). //numCheck == 1 ? "pricecheck" : "multipricecheck" + replace(signInfo.isBuy ? "" : "", String.format("%.2f", total /*numCheck > 0 ? total / numCheck : 0*/)). + replace("", itemN). + replace("", BetterShop.getSettings().currency()). + replace(signInfo.isBuy ? "" : "", BSEcon.format(total /*numCheck > 0 ? total / numCheck : 0*/)). + replace("", String.valueOf(numCheck))); + + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + BSutils.sendMessage(event.getPlayer(), "Failed to lookup the price"); + } + + } else if (BSPermissions.hasPermission(event.getPlayer(), + BetterShopPermission.ADMIN_MAKESIGN, true)) { + // sign is not a shopsign: (if has permission) add sign + try { + ShopSign newSign = new ShopSign(clickedSign); + + // all checks passed: create sign + signsd.setSign(event.getClickedBlock().getLocation(), newSign); + newSign.updateColor(); + + BSutils.sendMessage(event.getPlayer(), "new sign created" + + (newSign.getCustomPrice() >= 0 ? " with a custom price of " + + BSEcon.format(newSign.getCustomPrice()) + " each" : "")); + + } catch (Exception e) { + BSutils.sendMessage(player, ChatColor.RED + "Error: " + e.getMessage()); + } + } + + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, e); + event.getPlayer().sendMessage("An Error Occured"); + } + } + } + } + + public boolean load() { + return signsd.load(); + } + + public boolean save() { + return !signsd.isChanged() || signsd.save(); + } + + public boolean saveDelayActive() { + return signsd.saveDelayActive(); + } + + public int numSigns() { + return signsd.getSavedSigns().size(); + } + + public void startProtecting() { + checkSigns.start(signResWait); + } + + public void stopProtecting() { + checkSigns.cancel(); + } + /* + public class StopBreak extends BlockListener { + ] + Location toStop = null; + + public void stopPlace(Location loc) { + toStop = loc.clone(); + } + + @Override + public void onBlockCanBuild(BlockCanBuildEvent event) { + if (event.getBlock().getLocation().equals(toStop)) { + event.setBuildable(false); + } + } + }//*/ + /* + public class UpdateInv extends TimerTask { + + Player toupdate = null; + + public UpdateInv(Player p) { + toupdate = p; + } + + public void start(long wait) { + (new Timer()).schedule(this, wait); + } + + @Override + public void run() { + toupdate.getInventory().setContents(toupdate.getInventory().getContents()); + } + }//*/ +} // end class BSSignShop + diff --git a/src/me/jascotty2/bettershop/signshop/ShopSign.java b/src/me/jascotty2/bettershop/signshop/ShopSign.java new file mode 100644 index 0000000..3a0be19 --- /dev/null +++ b/src/me/jascotty2/bettershop/signshop/ShopSign.java @@ -0,0 +1,270 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for storing info about a shop sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.signshop; + +import java.sql.SQLException; +import java.util.List; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.commands.SellCommands; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.util.Str; +import org.bukkit.ChatColor; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class ShopSign { + + public final static String SIGN_TEXT = "[BetterShop]"; + private final Sign sign; + public String itemName; // final + JItem item = null, catItems[] = null; + boolean isInv, inHand, isBuy, isCategory; + double customPrice = -1; + int amount; + + public ShopSign(Sign s) { + + if (s == null) { + throw new IllegalArgumentException("Sign cannot be null!"); + } else if (!ChatColor.stripColor(s.getLine(0)).equalsIgnoreCase(SIGN_TEXT)) { + throw new IllegalArgumentException("Invalid Sign!"); + } + this.sign = s; + //try { + String action = ChatColor.stripColor(sign.getLine(1).trim()).replace(" ", " "); + + // quick check for valid sign action + if (Str.count(action, " ") > 1 + || (action.contains(" ") && !Str.startIsIn(action, new String[]{ + "buy ", "buyall ", "buystack ", + "sell ", "sellall ", "sellstack "})) + || (!action.contains(" ") && !Str.startIsIn(action, new String[]{ + "buy", "buyall", "buystack", + "sell", "sellall", "sellstack"}))) { + throw new IllegalArgumentException("Invalid Sign! (invalid action: " + action + ")"); + } + isBuy = action.toLowerCase().startsWith("buy"); + + // first find the item(s) + String searchItem = ChatColor.stripColor(sign.getLine(2).toLowerCase().replace(" ", "")); + JItem toAdd[]; + // check if is a category + if(!searchItem.isEmpty() && (itemName = JItemDB.findCategory(searchItem)) != null) { + toAdd = JItemDB.getItemsByCategory(searchItem); + catItems = toAdd; + isCategory = true; + } else { + toAdd = searchItem.isEmpty() ? new JItem[]{null} : new JItem[]{JItemDB.findItem(searchItem)}; + if(toAdd[0] == null) toAdd = JItemDB.findItems(searchItem); + if (toAdd.length == 1 && toAdd[0] != null) { + item = toAdd[0]; + itemName = item.Name(); + } else if (toAdd.length > 1) { +// System.out.println("matches for " + searchItem); +// for(JItem i : toAdd){ +// System.out.println(i == null ? "null " : i.Name()); +// } + throw new IllegalArgumentException("Invalid Sign! (multiple items match query)"); + } else { + itemName = null; + } + } + + isInv = searchItem.equals("inv") || action.contains("inv"); + inHand = searchItem.equals("hand") || searchItem.equals("inhand") || action.contains("hand"); + + if (action.contains("all")) { + amount = -1; + } else { + String amt = action.contains(" ") + ? action.substring(action.lastIndexOf(' ')).trim().toLowerCase() : "1"; + amount = CheckInput.GetInt(amt, -1); + if (action.contains("stack")) { + if (item == null && catItems == null) { + // can't buy/sell stacks of nothing + throw new IllegalArgumentException("Invalid Sign! (invalid item: " + searchItem + ")"); + } + amount = item != null ? (amount <= 0 + ? (BetterShop.getSettings().usemaxstack ? item.MaxStackSize() : 64) + : amount * (BetterShop.getSettings().usemaxstack ? item.MaxStackSize() : 64)) + : 64; + } + + if (amount <= 0) { + throw new IllegalArgumentException("Invalid Sign! (bad amount: " + amt + ")"); + } + } + + // now check if there's a custom price for the transaction + String customPriceStr = ChatColor.stripColor(sign.getLine(3).trim()); + if (!customPriceStr.isEmpty()) { + customPrice = CheckInput.ExtractDouble(customPriceStr, -1); + if(amount > 0){ + customPrice /= amount; + } + } + + if (isBuy) { + if (item == null && catItems == null) { + // can't buy nothing + throw new IllegalArgumentException("Invalid Sign! (invalid item: " + searchItem + ")"); + } + } else { + // is selling + if ((amount > 0 && item == null && catItems == null && inHand == false) + || (item != null && item.ID() <= 0)) { + // can't sell nothing + throw new IllegalArgumentException("Invalid Sign! (invalid item: " + searchItem + ")"); + } else if (item != null && item.isKit()) { + throw new IllegalArgumentException("Invalid Sign! (Kits cannot be sold)"); + } else if (item != null && item.isEntity()) { + throw new IllegalArgumentException("Invalid Sign! (Entities cannot be sold)"); + } + } + +// } catch (Exception e) { +// Logger.getAnonymousLogger().warning(e.toString() + " " + e.getMessage() + "\n" + Str.getStackStr(e)); +// } + } + + public void updateColor() { + + //sign.setLine(0, BetterShop.getConfig().activeSignColor + SIGN_TEXT); + + boolean up = false; + if (!sign.getLine(0).startsWith(BetterShop.getSettings().activeSignColor)) { + sign.setLine(0, BetterShop.getSettings().activeSignColor + SIGN_TEXT); + up = true; + } + if (BetterShop.getSettings().signItemColor && item != null + && item.color != null && !sign.getLine(2).startsWith(item.color)) { + if (BetterShop.getSettings().signItemColorBWswap) { + if (ChatColor.BLACK.toString().equals(item.color)) { + if (!sign.getLine(2).startsWith(ChatColor.WHITE.toString())) { + sign.setLine(2, ChatColor.WHITE + ChatColor.stripColor(sign.getLine(2))); + up = true; + } + } else if (ChatColor.WHITE.toString().equals(item.color)) { + if (!sign.getLine(2).startsWith(ChatColor.BLACK.toString())) { + sign.setLine(2, ChatColor.BLACK + ChatColor.stripColor(sign.getLine(2))); + up = true; + } + } else if (!sign.getLine(2).startsWith(item.color)) { + sign.setLine(2, item.color + ChatColor.stripColor(sign.getLine(2))); + up = true; + } + } + } + if (up) { + sign.update(); + } + } + + public JItem getItem() { + return item; + } + + public JItem getItem(Player p) { + return inHand && p.getItemInHand() != null + && p.getItemInHand().getAmount() > 0 + ? JItemDB.GetItem(p.getItemInHand()) : null; + } + + double priceCheck(Player player) throws SQLException, Exception { + if (customPrice >= 0) { + return customPrice; + } + // now find the price of the transaction + double price = -1; + Shop shop = BetterShop.getShop(sign.getBlock().getLocation()); + if (isBuy) { + int buyAmt = amount; + if (item != null && !item.IsLegal() + && (BetterShop.getSettings().allowbuyillegal + || BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL, false))) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("illegalbuy"). + replace("", item.coloredName())); + return -1; + } else if (item != null && !shop.pricelist.isForSale(item)) { + BSutils.sendMessage(player, + BetterShop.getSettings().getString("notforsale"). + replace("", item.coloredName())); + return -1; + } + if (amount < 0) { // buyall + // get max. can buy + buyAmt = BSutils.amtCanHold(player, item); + long avail = -1; + + if (shop.config.useStock()) { + try { + avail = shop.stock.getItemAmount(item); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + if (avail == 0) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("outofstock"). + replace("", item.coloredName())); + return -1; + } else if (avail >= 0 && amount > avail) { + BSutils.sendMessage(player, BetterShop.getSettings().getString("lowstock"). + replace("", item.coloredName()). + replace("", String.valueOf(avail))); + buyAmt = (int) avail; + } + } + } + // should now have amount to buy + price = shop.pricelist.itemBuyPrice(player, item, buyAmt); + } else { + // is selling + if (amount < 0) { + //playerAmount = 0; + List sellable = SellCommands.getCanSell(player, + isInv, item == null ? null : item == null ? catItems : new JItem[]{item}, customPrice); + for (ItemStack it : sellable) { + //playerAmount += it.getAmount(); + price += shop.pricelist.itemSellPrice(player, it, it.getAmount()); + } + } else { + //playerAmount = BSutils.amtHas(player, item); + price = shop.pricelist.itemSellPrice(player, item, BSutils.amtHas(player, item)); + } + } + return price; + } + + public Sign getSign() { + return sign; + } + + public double getCustomPrice() { + return customPrice; + } + +} // end class SignItem + diff --git a/src/me/jascotty2/bettershop/signshop/SignDB.java b/src/me/jascotty2/bettershop/signshop/SignDB.java new file mode 100644 index 0000000..4adab35 --- /dev/null +++ b/src/me/jascotty2/bettershop/signshop/SignDB.java @@ -0,0 +1,322 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: sign shop database + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.signshop; + +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.utils.BetterShopLogger; + +import me.jascotty2.lib.io.FileIO; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.bukkit.item.JItem; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; + +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; + +/** + * @author jacob + */ +public class SignDB { + + public final static long signDBsaveWait = 30000; // don't save immediately, wait (30s) + HashMap signs = new HashMap(); + HashMap savedSigns = new HashMap(); + HashMap signBlocks = new HashMap(); + boolean changed = false; + private SignSaver delaySaver = null; + final Server server; + + public SignDB(Server sv) { + if (sv == null) { + throw new IllegalArgumentException("Plugin Cannot be Null"); + } + server = sv; + } + + public boolean load() { + if (BSConfig.signDBFile.exists()) { + try { + List signdb = FileIO.loadCSVFile(BSConfig.signDBFile); + try { + for (String[] s : signdb) { + if (s.length >= 4 && server.getWorld(s[0]) != null) { + try { + Location l = new Location(server.getWorld(s[0]), + CheckInput.GetDouble(s[1], 0), + CheckInput.GetDouble(s[2], 0), + CheckInput.GetDouble(s[3], 0)); + if (l.getBlock().getState() instanceof Sign + && ChatColor.stripColor(((Sign) l.getBlock().getState()).getLine(0)).equalsIgnoreCase("[BetterShop]")) { + Sign checkSign = (Sign) l.getBlock().getState(); + ShopSign signInfo = new ShopSign(checkSign); + signs.put(l, signInfo); + + // save sign + savedSigns.put(l.clone(), checkSign); + + // save block that anchors it + if (l.getBlock().getType() == Material.SIGN_POST) { + signBlocks.put(l.getBlock().getRelative(BlockFace.DOWN).getLocation(), + l.getBlock().getRelative(BlockFace.DOWN).getState()); + } else { + Block a = getSignAnchor(l.getBlock()); + if (a != null) { + signBlocks.put(a.getLocation(), a.getState()); + } + } + + boolean up = false; + if (!checkSign.getLine(0).startsWith(BetterShop.getSettings().activeSignColor)) { + checkSign.setLine(0, BetterShop.getSettings().activeSignColor + ChatColor.stripColor(checkSign.getLine(0))); + up = true; + } + if (BetterShop.getSettings().signItemColor) { + JItem i = signInfo.getItem(); + if (i != null && i.color != null && !checkSign.getLine(2).startsWith(i.color)) { + if (BetterShop.getSettings().signItemColorBWswap && ChatColor.BLACK.toString().equals(i.color)) { + checkSign.setLine(2, ChatColor.WHITE + ChatColor.stripColor(checkSign.getLine(2))); + } else if (BetterShop.getSettings().signItemColorBWswap && ChatColor.WHITE.toString().equals(i.color)) { + checkSign.setLine(2, ChatColor.BLACK + ChatColor.stripColor(checkSign.getLine(2))); + } else { + checkSign.setLine(2, i.color + ChatColor.stripColor(checkSign.getLine(2))); + } + up = true; + } + } + + if (up) { + checkSign.update(); + } + } + } catch (Exception e) { + BetterShopLogger.Severe("Invalid Sign while Loading: " + e.getMessage()); + } + } + } + + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, "Unexpected Error while Loading Signs", e); + } + + return true; + } catch (FileNotFoundException ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } catch (IOException ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + } + return false; + } + return true; + } + + public boolean save() { + try { + if (delaySaver != null) { + delaySaver.cancel(); + delaySaver = null; + } + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, e); + } + try { + ArrayList file = new ArrayList(); + for (Location l : signs.keySet().toArray(new Location[0])) { + file.add(l.getWorld().getName() + "," + + l.getBlockX() + "," + l.getBlockY() + "," + l.getBlockZ()); + } + return FileIO.saveFile(BSConfig.signDBFile, file) && !(changed = false); + } catch (Exception e) { + BetterShopLogger.Log(Level.SEVERE, e); + } + return false; + } + + public void setSign(Location l) { + if (l != null && l.getBlock().getState() instanceof Sign) { + setSign(l, new ShopSign((Sign) l.getBlock().getState())); + } + } + + public void setSign(Location l, ShopSign s) { + if (l != null && s != null && l.getBlock().getState() instanceof Sign) { + signs.put(l.clone(), s); + savedSigns.put(l.clone(), (Sign) l.getBlock().getState()); + Block a = getSignAnchor(l.getBlock()); + if (a != null) { + signBlocks.put(a.getLocation(), a.getState()); + } + changed = true; + delaySave(); + } + } + + public void remove(Location l) { + signs.remove(l); + savedSigns.remove(l); + Block b = getSignAnchor(l.getBlock()); + if (b != null) { + signBlocks.remove(b.getLocation()); + } + changed = true; + delaySave(); + } + + public boolean signExists(Location l) { + return signs.containsKey(l); + } + + public boolean isSignAnchor(Location l) { + for(BlockState b : signBlocks.values()) { + if(b.getBlock().getLocation().equals(l)) { + return true; + } + } + return false; + } + + public boolean isSignAnchor(Block bl) { + for(BlockState b : signBlocks.values()) { + if(b.getBlock().equals(bl)) { + return true; + } + } + return false; + } + + public ShopSign getSignShop(Location l) { + return signs.get(l); + } + + public List getShopSigns(Block b) { + ArrayList list = getSigns(b); + for (int i = 0; i < list.size(); ++i) { + if (!signs.containsKey(list.get(i).getLocation())) { + list.remove(i); + --i; + } + } + return list; + } + + public Collection getSignAnchors() { + return signBlocks.values(); + } + + public Collection getSavedSigns() { + return savedSigns.values(); + } + + public void delaySave() { + if (delaySaver != null) { + delaySaver.cancel(); + } + delaySaver = new SignSaver(); + delaySaver.start(signDBsaveWait); + } + + public boolean saveDelayActive() { + return delaySaver != null; + } + + protected class SignSaver extends TimerTask { + + public void start(long wait) { + (new Timer()).schedule(this, wait); + } + + @Override + public void run() { + save(); + } + } + +// final static BlockFace checkFaces[] = new BlockFace[]{BlockFace.SELF, BlockFace.UP, +// BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + public static ArrayList getSigns(Block b) { + ArrayList list = new ArrayList(); + if (b.getState() instanceof Sign) { + list.add(b); + } else { + if (b.getRelative(BlockFace.UP).getType() == Material.SIGN_POST) { + list.add(b.getRelative(BlockFace.UP)); + } + if (b.getRelative(BlockFace.NORTH).getType() == Material.WALL_SIGN + && b.getRelative(BlockFace.NORTH).getData() == 4) { + list.add(b.getRelative(BlockFace.NORTH)); + } + if (b.getRelative(BlockFace.SOUTH).getType() == Material.WALL_SIGN + && b.getRelative(BlockFace.SOUTH).getData() == 5) { + list.add(b.getRelative(BlockFace.SOUTH)); + } + + if (b.getRelative(BlockFace.WEST).getType() == Material.WALL_SIGN + && b.getRelative(BlockFace.WEST).getData() == 3) { + list.add(b.getRelative(BlockFace.WEST)); + } + + if (b.getRelative(BlockFace.EAST).getType() == Material.WALL_SIGN + && b.getRelative(BlockFace.EAST).getData() == 2) { + list.add(b.getRelative(BlockFace.EAST)); + } + + //BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST + } + return list; + } + + public static Block getSignAnchor(Block b) { + if (b.getTypeId() == 68) { + switch (b.getData()) { + case 2: // w + return b.getRelative(BlockFace.WEST); + case 3: // e + return b.getRelative(BlockFace.EAST); + case 4: // s + return b.getRelative(BlockFace.SOUTH); + case 5: // n + return b.getRelative(BlockFace.NORTH); + } + } else if (b.getTypeId() == 63) { + return b.getRelative(BlockFace.DOWN); + } + return null; + } + + public boolean isChanged() { + return changed; + } +} // end class SignDB + diff --git a/src/me/jascotty2/bettershop/signshop/SignRestore.java b/src/me/jascotty2/bettershop/signshop/SignRestore.java new file mode 100644 index 0000000..b0d976b --- /dev/null +++ b/src/me/jascotty2/bettershop/signshop/SignRestore.java @@ -0,0 +1,140 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for checking & maintaining signs + * (can be used to prevent accidental destruction / deletion of shop signs) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.signshop; + +import java.util.List; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import org.bukkit.Server; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityExplodeEvent; + +/** + * @author jacob + */ +public class SignRestore implements Listener, Runnable { + + final Plugin plugin; + final Server server; + final SignDB signs; + int taskID = -1; + + public SignRestore(Plugin p, SignDB signs) { + if (p == null || signs == null) { + throw new IllegalArgumentException("Arguments Cannot be Null"); + } + plugin = p; + server = p.getServer(); + this.signs = signs; + } + + public void start(long wait) { + //(new Timer()).scheduleAtFixedRate(this, wait, wait); + // 20 ticks per second + taskID = server.getScheduler().scheduleSyncRepeatingTask(plugin, this, 100, (wait * 20) / 1000); + } + + public void cancel() { + if (taskID != -1) { + server.getScheduler().cancelTask(taskID); + taskID = -1; + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockPlace(BlockPlaceEvent event) { + if (!event.isCancelled() && signs.signExists(event.getBlock().getLocation())) { + signs.remove(event.getBlock().getLocation()); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent event) { + if (!event.isCancelled() && signs.signExists(event.getBlock().getLocation())) { + if (BetterShop.getSettings().signDestroyProtection + && !BSPermissions.hasPermission(event.getPlayer(), BetterShopPermission.ADMIN_MAKESIGN, true)) { + event.setCancelled(true); + } else { + signs.remove(event.getBlock().getLocation()); + if (!(event.getBlock().getState() instanceof Sign)) { + List list = signs.getShopSigns(event.getBlock()); + for (Block b : list) { + signs.remove(b.getLocation()); + } + } + } + } + } + + @Override + public void run() { + for (BlockState b : signs.getSignAnchors()) { + if (b.getBlock().getLocation().getBlock().getTypeId() != b.getTypeId()) { + b.getBlock().getLocation().getBlock().setTypeIdAndData(b.getTypeId(), b.getRawData(), false); + } + } + for (Sign b : signs.getSavedSigns()) { + if (b.getBlock().getLocation().getBlock().getTypeId() != b.getTypeId()) { + b.getBlock().getLocation().getBlock().setTypeIdAndData(b.getTypeId(), b.getRawData(), false); + Sign dest = (Sign) b.getBlock().getLocation().getBlock().getState(); + for (int i = 0; i < 4; ++i) { + dest.setLine(i, b.getLine(i)); + } + dest.update(); + } + } + } + + @EventHandler + public void onEntityExplode(EntityExplodeEvent event) { + if (!event.isCancelled() && BetterShop.getSettings().signTNTprotection) { + for (Block b : event.blockList()) { + if (signs.signExists(b.getLocation())) { + event.setCancelled(true); + return; + } + } + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onEntityBlockInteract(EntityInteractEvent event) { + // ought to work for enderman pickup and other break events, but not sure how to test.. + if (!event.isCancelled() && event.getBlock() != null + && !(event.getEntity() instanceof Player) + && BetterShop.getSettings().signDestroyProtection) { + if (signs.signExists(event.getBlock().getLocation()) || signs.isSignAnchor(event.getBlock())) { + event.setCancelled(true); + return; + } + } + } +} +// end class SignRestore diff --git a/src/me/jascotty2/bettershop/spout/SpoutKeyListener.java b/src/me/jascotty2/bettershop/spout/SpoutKeyListener.java new file mode 100644 index 0000000..3aaadb4 --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/SpoutKeyListener.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for opening & closing shop menu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.spout; + +import me.jascotty2.lib.io.CheckInput; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.BSPermissions; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.getspout.spoutapi.event.input.KeyPressedEvent; +import org.getspout.spoutapi.gui.ScreenType; +import org.getspout.spoutapi.keyboard.Keyboard; + +public class SpoutKeyListener implements Listener { + + static HashMap keys = new HashMap(); + + { + keys.put("'", "APOSTROPHE"); + keys.put("+", "ADD"); + keys.put("@", "AT"); + keys.put("\\", "BACKSLASH"); + keys.put(":", "COLON"); + keys.put(",", "COMMA"); + keys.put("/", "SLASH"); + keys.put("=", "EQUALS"); + keys.put("`", "GRAVE"); + keys.put("[", "LBRACKET"); + keys.put("-", "MINUS"); + keys.put("*", "MULTIPLY"); + keys.put(".", "PERIOD"); + keys.put("^", "POWER"); + keys.put("]", "RBRACKET"); + keys.put(";", "SEMICOLON"); + keys.put(" ", "SPACE"); + keys.put("-", "SUBTRACT"); + keys.put("_", "UNDERLINE"); + keys.put("\t", "TAB"); + } + Keyboard listenKey = null; + boolean keyPressError = false; + + public SpoutKeyListener() { + reloadKey(); + } // end default constructor + + public final void reloadKey() { + String k = BetterShop.getSettings().getSpoutKey().toUpperCase(); + try { + if (CheckInput.IsInt(k)) { + listenKey = Keyboard.getKey(CheckInput.GetInt(k, -1)); + } else { + k = k.replace("KEY_", ""); + if (k.length() > 1 || Character.isLetterOrDigit(k.charAt(0))) { + for (Entry ky : keys.entrySet()) { + if (k.equals(ky.getKey())) { + k = ky.getValue(); + break; + } + } + } + listenKey = Keyboard.valueOf("KEY_" + k); + } + } catch (Exception e) { + BetterShopLogger.Log(Level.WARNING, "Invalid Key in Spout Config: defaulting to 'B'"); + listenKey = Keyboard.KEY_B; + } + } + + @EventHandler + public void onKeyPressedEvent(KeyPressedEvent event) { + if (!BetterShop.getSettings().spoutEnabled) { + return; + } + try { + if (event.getKey() == Keyboard.KEY_ESCAPE) { + SpoutPopupDisplay.closePopup(event.getPlayer()); + } else if (event.getKey() == listenKey) { + if (event.getScreenType() == ScreenType.GAME_SCREEN) { + if (BSPermissions.hasPermission(event.getPlayer(), BetterShopPermission.USER_SPOUT, true)) { + if (BetterShop.commandShopEnabled(event.getPlayer().getLocation())) { + SpoutPopupDisplay.popup(event.getPlayer()); + } else { + BSutils.sendMessage(event.getPlayer(), + BetterShop.getSettings().getString("regionShopDisabled")); + } + } + } + } +// else if(event.getKey() == Keyboard.KEY_P) { SpoutPopupDisplay.testScreen(event.getPlayer()); } + } catch (Exception e) { + BetterShopLogger.Severe("Unexpected error in KeyListener", e); + keyPressError = true; + } + } +} // end class SpoutKeyListener + diff --git a/src/me/jascotty2/bettershop/spout/SpoutPopupDisplay.java b/src/me/jascotty2/bettershop/spout/SpoutPopupDisplay.java new file mode 100644 index 0000000..78b5bea --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/SpoutPopupDisplay.java @@ -0,0 +1,735 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for displaying the spout gui shop interface + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.spout; + +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.bettershop.BSPermissions; + +import me.jascotty2.lib.bukkit.item.PriceListItem; +import me.jascotty2.lib.bukkit.MinecraftChatStr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import me.jascotty2.bettershop.enums.SpoutCategoryMethod; +import me.jascotty2.bettershop.spout.gui.*; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; + +import org.getspout.spoutapi.event.screen.TextFieldChangeEvent; +import org.getspout.spoutapi.gui.Button; +import org.getspout.spoutapi.gui.Color; +import org.getspout.spoutapi.gui.GenericButton; +import org.getspout.spoutapi.gui.GenericContainer; +import org.getspout.spoutapi.gui.GenericGradient; +import org.getspout.spoutapi.gui.GenericLabel; +import org.getspout.spoutapi.gui.GenericPopup; +import org.getspout.spoutapi.gui.GenericSlider; +import org.getspout.spoutapi.gui.InGameHUD; +import org.getspout.spoutapi.gui.RenderPriority; +import org.getspout.spoutapi.gui.Slider; +import org.getspout.spoutapi.gui.TextField; +import org.getspout.spoutapi.player.SpoutPlayer; + +/** + * @author jacob + */ +public class SpoutPopupDisplay { + + static Map popupOpen = new HashMap(); + protected static int xPad = 15, yPad = 8; + int height = 160, width = 420, maxRows, maxCols; + protected static final int MAX_WIDTH = 427, MAX_HEIGHT = 240; + SpoutPlayer player; + // gui objects + GenericButton btnExit = new GenericButton(), + btnAbout = new GenericButton(); + //GenericContainer items = new GenericContainer(); + GenericSlider itemScroll = new GenericSlider(); + GenericButton btnScrollLeft = new GenericButton(), + btnScrollRight = new GenericButton(); + GenericLabel lblPageNum = new GenericLabel(); + GenericPopup popup = new GenericPopup(); + List> menuPages = new ArrayList>(); //new ArrayList(); + List menuItems = new ArrayList(); + int currentPage = 0; + int xpos[]; + MarketItemDetail itemDetail; + boolean aboutActive = false; + boolean isPaged = true; + boolean useScroll = BetterShop.getSettings().spoutUseScroll; + int numPages; + // gradient :) + GenericGradient gradient = new GenericGradient(); + // categories + int catNum = 0; + String[] categories; + // cycle + GenericButton btnCatCycle = null; + // tabbed + int tabPage = 0, tabSize = 60, tabXpad = 5, tabPageSize = (MAX_WIDTH - 20) / (tabSize + tabXpad); + GenericButton btnTabLeft, btnTabRight; + List tabButtons = new ArrayList(); + + public SpoutPopupDisplay(SpoutPlayer p) { + player = p; + itemDetail = new MarketItemDetail(p); + isPaged = BetterShop.getSettings().spoutUsePages; + } // end default constructor + + public static void popup(SpoutPlayer p) { + if (popupOpen.containsKey(p)) { + closePopup(p); + return; + } + BetterShop.checkRestock(); + SpoutPopupDisplay d = new SpoutPopupDisplay(p); + //System.out.println(p.getMainScreen().getHeight() + " x " + p.getMainScreen().getWidth()); + popupOpen.put(p, d); + d.show(); + } + + public static void closePopup(SpoutPlayer p) { + if (popupOpen.containsKey(p)) { + SpoutPopupDisplay d = popupOpen.get(p); + if (d.aboutActive) { + d.popup.removeWidgets(BetterShop.getPlugin()); + d.show(); + d.aboutActive = false; + } else { + p.getMainScreen().closePopup(); + d.close(); + popupOpen.remove(p); + } + } + } + + public static SpoutPopupDisplay getPopup(SpoutPlayer p) { + return popupOpen.get(p); + } + + public static void testScreen(SpoutPlayer p) { + InGameHUD hudscreen = p.getMainScreen(); + GenericPopup popup = new GenericPopup(); + popup.setVisible(true); + + int height = 160, width = 420, maxRows, maxCols; + + GenericButton btnExit = new GenericButton(), + btnAbout = new GenericButton(); + + //Exit Button + btnExit.setText("EXIT").setWidth(45).setHeight(15).setX(378).setY(222); + popup.attachWidget(BetterShop.getPlugin(), btnExit); + + //"About" :) + btnAbout.setText("?").setWidth(12).setHeight(12).setX(MAX_WIDTH - 2).setY(MAX_HEIGHT - 2); + popup.attachWidget(BetterShop.getPlugin(), btnAbout); + + String[] categories; + GenericButton btnCatCycle = null; + // tabbed + int tabPage = 0, tabSize = 60, tabXpad = 5, tabPageSize = (MAX_WIDTH - 20) / (tabSize + tabXpad); + GenericButton btnTabLeft, btnTabRight; + List tabButtons = new ArrayList(); + + String cats[] = JItemDB.getCategories(); + categories = new String[cats.length + 1]; + categories[0] = "All"; + System.arraycopy(cats, 0, categories, 1, cats.length); + + if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.CYCLE) { + btnCatCycle = new GenericButton(); + btnCatCycle.setTooltip("Category"); + btnCatCycle.setWidth(50).setHeight(15).setX(374).setY(205); + popup.attachWidget(BetterShop.getPlugin(), btnCatCycle); + } else if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.TABBED) { + btnTabLeft = new GenericButton(); + btnTabRight = new GenericButton(); + btnTabLeft.setText("<").setHeight(10).setWidth(10).setY(4).setX(2); + btnTabLeft.setEnabled(false).setVisible(false); + btnTabRight.setText(">").setHeight(10).setWidth(10).setY(4).setX(MAX_WIDTH - 12); + btnTabRight.setEnabled(false).setVisible(false); + + popup.attachWidget(BetterShop.getPlugin(), btnTabLeft); + popup.attachWidget(BetterShop.getPlugin(), btnTabRight); + + int x = 20; + boolean vis = true; + for (String c : categories) { + GenericButton tab = new GenericButton(c); + tab.setHeight(10).setWidth(tabSize).setY(4).setX(x); + tab.setEnabled(vis).setVisible(vis); + x += tabSize + 5; + if (x + tabSize >= MAX_WIDTH - 15) { + x = 20; + vis = false; + btnTabRight.setEnabled(true).setVisible(true); + } + tabButtons.add(tab); + popup.attachWidget(BetterShop.getPlugin(), tab); + } + height -= 15; + } + + GenericSlider itemScroll = new GenericSlider(); + GenericButton btnScrollLeft = new GenericButton(), + btnScrollRight = new GenericButton(); + // GenericSlider is not vertical :( + //itemScroll.setHeight(350).setWidth(5).setX(100); + itemScroll.setHeight(8).setWidth(width - 20); + itemScroll.setY(height).setX((MAX_WIDTH - (width - 20)) / 2); + itemScroll.setSliderPosition(0); + + btnScrollLeft.setText("<").setHeight(10).setWidth(10).setY(height).setX(2); + btnScrollRight.setText(">").setHeight(10).setWidth(10).setY(height).setX(MAX_WIDTH - 12); + + GenericLabel lblPageNum = new GenericLabel(); + lblPageNum.setHeight(7).setWidth(40).setY(height + 11).setX(MAX_WIDTH - 75); + lblPageNum.setTextColor(new Color(.65F, .65F, .65F)); + + + MarketItemDetail itemDetail = new MarketItemDetail(p); + itemDetail.setX(2).setY(MAX_HEIGHT - itemDetail.getHeight()); + itemDetail.updateItem(1, (byte) 0); + + popup.attachWidget(BetterShop.getPlugin(), itemScroll); + popup.attachWidget(BetterShop.getPlugin(), btnScrollLeft); + popup.attachWidget(BetterShop.getPlugin(), btnScrollRight); + popup.attachWidget(BetterShop.getPlugin(), lblPageNum); + popup.attachWidget(BetterShop.getPlugin(), itemDetail); + + hudscreen.attachPopupScreen(popup); + hudscreen.updateWidget(popup); + hudscreen.setDirty(true); + } + + public void close() { + player = null; + btnExit = null; + } + + public void show() { + + InGameHUD hudscreen = player.getMainScreen(); + + popup.setVisible(true); + + //Exit Button + btnExit.setText("EXIT").setWidth(45).setHeight(15).setX(378).setY(222); + popup.attachWidget(BetterShop.getPlugin(), btnExit); + + //"About" :) + btnAbout.setText("?").setWidth(12).setHeight(12).setX(MAX_WIDTH - 4).setY(MAX_HEIGHT - 4); + popup.attachWidget(BetterShop.getPlugin(), btnAbout); + + String cats[] = JItemDB.getCategories(); + categories = new String[cats.length + 1]; + categories[0] = "All"; + System.arraycopy(cats, 0, categories, 1, cats.length); + + if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.CYCLE) { + btnCatCycle = new GenericButton(); + btnCatCycle.setTooltip("Category"); + btnCatCycle.setWidth(50).setHeight(15).setX(374).setY(205); + popup.attachWidget(BetterShop.getPlugin(), btnCatCycle); + } else if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.TABBED) { + btnTabLeft = new GenericButton(); + btnTabRight = new GenericButton(); + btnTabLeft.setText("<").setHeight(10).setWidth(10).setY(4).setX(2); + btnTabLeft.setEnabled(false).setVisible(false); + btnTabRight.setText(">").setHeight(10).setWidth(10).setY(4).setX(MAX_WIDTH - 12); + btnTabRight.setEnabled(false).setVisible(false); + + popup.attachWidget(BetterShop.getPlugin(), btnTabLeft); + popup.attachWidget(BetterShop.getPlugin(), btnTabRight); + + int x = 20; + boolean vis = true; + for (String c : categories) { + GenericButton tab = new GenericButton(c); + tab.setHeight(10).setWidth(tabSize).setY(4).setX(x); + tab.setEnabled(vis).setVisible(vis); + x += tabSize + 5; + if (x + tabSize >= MAX_WIDTH - 15) { + x = 20; + vis = false; + btnTabRight.setEnabled(true).setVisible(true); + } + tabButtons.add(tab); + popup.attachWidget(BetterShop.getPlugin(), tab); + } + height -= 15; + } + + showCategory(0); + + btnScrollLeft.setText("<").setHeight(10).setWidth(10).setY(height). + setX(useScroll ? 2 : MAX_WIDTH - 90); + btnScrollRight.setText(">").setHeight(10).setWidth(10).setY(height).setX(MAX_WIDTH - 12); + + lblPageNum.setHeight(7).setWidth(40).setY(height + 11).setX(MAX_WIDTH - 75); + lblPageNum.setTextColor(new Color(.65F, .65F, .65F)); + + itemDetail.setX(2).setY(MAX_HEIGHT - itemDetail.getHeight()); //height + 15); + + gradient.setPriority(RenderPriority.Highest); + gradient.setTopColor(new Color(0.5F, 0.5F, 0.5F, 0.4F)). + setBottomColor(new Color(0.2F, 0.2F, 0.2F, 0.4F)); + gradient.setX(xPad / 2).setY(yPad / 2).setWidth(MAX_WIDTH - xPad).setHeight(height - yPad); + + if (useScroll) { + // GenericSlider is not vertical :( + //itemScroll.setHeight(350).setWidth(5).setX(100); + itemScroll.setHeight(8).setWidth(width - 20); + itemScroll.setY(height).setX((MAX_WIDTH - (width - 20)) / 2); + itemScroll.setSliderPosition(0); + popup.attachWidget(BetterShop.getPlugin(), itemScroll); + } + popup.attachWidget(BetterShop.getPlugin(), btnScrollLeft); + popup.attachWidget(BetterShop.getPlugin(), btnScrollRight); + popup.attachWidget(BetterShop.getPlugin(), lblPageNum); + popup.attachWidget(BetterShop.getPlugin(), itemDetail); + popup.attachWidget(BetterShop.getPlugin(), gradient); + + hudscreen.attachPopupScreen(popup); + hudscreen.updateWidget(popup); + hudscreen.setDirty(true); + } + + protected void showAbout() { + aboutActive = true; + //player.getMainScreen().closePopup(); + + popup.removeWidgets(BetterShop.getPlugin()); + + popup.attachWidget(BetterShop.getPlugin(), btnExit); + + GenericLabel lblTitle = new GenericLabel(); + lblTitle.setText(MinecraftChatStr.padCenter("About", ' ', 45)).setX((MAX_WIDTH - 45) / 2). + setY(12).setWidth(45).setHeight(10); + popup.attachWidget(BetterShop.getPlugin(), lblTitle); + + GenericLabel lblAbout = new GenericLabel(); + lblAbout.setX((MAX_WIDTH - 200) / 2).setY((MAX_HEIGHT - 100) / 2).setWidth(200).setHeight(100); + String about = "BetterShop " + BetterShop.getPlugin().getDescription().getVersion() + "\n" + + "Coding by Jacob Scott (jascotty2) \n" + + "https://github.com/jascotty2/BetterShop \n\n\n\n" + + "Help Support Development! \n" + + "send a small PayPal donation to \n" + + " jascottytechie@gmail.com"; + String lines[] = about.split("\n"); + StringBuilder txt = new StringBuilder(); + for (int i = 0; i < lines.length;) { + txt.append(MinecraftChatStr.padCenter(lines[i], ' ', 200)); + if (++i < lines.length) { + txt.append("\n"); + } + } + lblAbout.setText(txt.toString()); + + popup.attachWidget(BetterShop.getPlugin(), lblAbout); + + + GenericGradient gradTitle = new GenericGradient(); + + gradTitle.setPriority(RenderPriority.Highest); + gradTitle.setTopColor(new Color(0.4F, 0.4F, 0.4F, 0.6F)). + setBottomColor(new Color(0.2F, 0.2F, 0.2F, 0.4F)); + gradTitle.setX(lblTitle.getX() - xPad). + setY(lblTitle.getY() - yPad). + setWidth(lblTitle.getWidth() + xPad * 2). + setHeight(lblTitle.getHeight() + yPad * 2); + + popup.attachWidget(BetterShop.getPlugin(), gradTitle); + + + GenericGradient gradText = new GenericGradient(); + + gradText.setPriority(RenderPriority.Highest); + gradText.setTopColor(new Color(0.4F, 0.4F, 0.4F, 0.6F)). + setBottomColor(new Color(0.2F, 0.2F, 0.2F, 0.4F)); + gradText.setX(lblAbout.getX() - xPad). + setY(lblAbout.getY() - yPad). + setWidth(lblAbout.getWidth() + xPad * 2). + setHeight(lblAbout.getHeight() + yPad * 2); + + popup.attachWidget(BetterShop.getPlugin(), gradText); + + + InGameHUD hudscreen = player.getMainScreen(); + hudscreen.attachPopupScreen(popup); + hudscreen.updateWidget(popup); + hudscreen.setDirty(true); + } + + protected void showCategory(int catNum) { + while (catNum >= categories.length) { + catNum -= categories.length; + } + if (catNum < 0) { + catNum = 0; + } + + this.catNum = catNum; + String cat = categories[catNum]; + if (btnCatCycle != null) { + btnCatCycle.setText(cat).setDirty(true); + } else if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.TABBED) { + for (int c = 0; c < tabButtons.size(); ++c) { + tabButtons.get(c).setTextColor( + c == catNum ? new Color(.95F, .95F, .95F)//.95F, .35F, .35F)//(float) 33 / 255, (float) 25 / 255, (float) 210 / 255) + : new Color(.7F, .7F, .7F)).setDirty(true); + } + } + + clearDisplay(); + + ArrayList items = new ArrayList(); + + try { + PriceListItem allitems[] = BetterShop.getPricelist(player.getLocation()).getPricelistItems( + BSPermissions.hasPermission(player, BetterShopPermission.ADMIN_ILLEGAL)); + if (catNum == 0) { + items.addAll(Arrays.asList(allitems)); + } else { + for (PriceListItem p : allitems) { + if (p.HasCategory(cat)) { + items.add(p); + } + } + if (catNum > 0 && BetterShop.getSettings().spoutCatCustomSort) { + final List catItems = JItemDB.getCategory(cat); + if (catItems != null) { + Collections.sort(items, new Comparator() { + + public int compare(PriceListItem o1, PriceListItem o2) { + return catItems.indexOf(o1) - catItems.indexOf(o2); + } + }); + } + } + } + } catch (Exception e) { + popup.attachWidget(BetterShop.getPlugin(), + new GenericLabel().setText("Error Loading Pricelist! \n " + + e.getMessage()).setTextColor( + new Color(240 / (float) 255, + 45 / (float) 255, + 45 / (float) 255)).setX(150).setY(100)).setPriority(RenderPriority.Lowest); + return; + } + + try { + + int x, y, ix, iy, dx = xPad, dy = yPad; + boolean vis = true, + lg = BetterShop.getSettings().largeSpoutMenu; + if (lg) { + dx += LargeMarketMenuItem.DEF_WIDTH; + dy += LargeMarketMenuItem.DEF_HEIGHT; + } else { + dx += SmallMarketMenuItem.DEF_WIDTH; + dy += SmallMarketMenuItem.DEF_HEIGHT; + } + maxCols = MAX_WIDTH / dx; + x = (MAX_WIDTH - (maxCols * dx) + xPad) / 2; + ix = x; + + maxRows = height / dy; + y = (height - (maxRows * dy) + yPad) / 2; + if (BetterShop.getSettings().spoutCategories == SpoutCategoryMethod.TABBED) { + y += 15; + } + iy = y; + + xpos = new int[maxCols]; + int col = 0; + + List page = new ArrayList(); + + for (PriceListItem p : items) { + ItemButtonContainer m; + if (lg) { + m = new LargeMarketMenuItem(p.ID(), (byte) p.Data()); + } else { + m = new SmallMarketMenuItem(p.ID(), (byte) p.Data()); + } + m.setEnabled(/*vis*/false).setY(y).setX(x);//.setWidth(wid).setHeight(hgt); + if (vis) { + delayShowList.add(m); + } + menuItems.add(m); + page.add(m); + //items.addChild(m); + popup.attachWidget(BetterShop.getPlugin(), (GenericContainer) m); + + y += dy; + if (y + dy >= height) { + if (vis) { + xpos[col] = x; + } + y = iy; + if (x + dx >= width) { + x = ix; + vis = false; + menuPages.add(page); + page = new ArrayList(); + } else { + x += dx; + ++col; + } + } + } + if (page.size() > 0) { + menuPages.add(page); + } + + numPages = isPaged ? menuPages.size() + : (int) Math.ceil(((float) menuItems.size() / maxRows) - (maxCols - 1)); + + lblPageNum.setText("Page 1 of " + numPages).setDirty(true); + + btnScrollLeft.setEnabled(false).setVisible(false).setDirty(true); + btnScrollRight.setEnabled(numPages > 1).setVisible(numPages > 1).setDirty(true); + + startShow(); + } catch (Exception e) { + popup.attachWidget(BetterShop.getPlugin(), + new GenericLabel().setText("Error Displaying Itemlist! \n " + + e.getMessage()).setTextColor( + new Color(240 / (float) 255, + 45 / (float) 255, + 45 / (float) 255)).setX(150).setY(100)).setPriority(RenderPriority.Lowest); + } + + } + /* temp fix for a new spout problem .. */ + ArrayList delayShowList = new ArrayList(); + java.util.Timer showDelay; + + private void startShow() { + showDelay = new java.util.Timer(); + showDelay.schedule(new java.util.TimerTask() { + + @Override + public void run() { + for (ItemButtonContainer m : delayShowList) { + m.setEnabled(true); + } + delayShowList.clear(); + } + }, 100); + } + + protected void clearDisplay() { + for (ItemButtonContainer m : menuItems) { + popup.removeWidget(m); + } + menuItems.clear(); + for (List mp : menuPages) { + mp.clear(); + } + menuPages.clear(); + numPages = 0; + +// itemScroll.setSliderPosition(0); + + itemDetail.setVisible(false); + } + + protected synchronized void showPage(int page) { + if (page < 0 || page > numPages) { + throw new IllegalArgumentException("Illegal page number: " + page); + } + if (isPaged) { + if (currentPage < menuPages.size()) { + for (ItemButtonContainer p : menuPages.get(currentPage)) { + p.setEnabled(false); + } + } + if (page < menuPages.size()) { + for (ItemButtonContainer p : menuPages.get(page)) { + p.setEnabled(true); + } + } + } else { + for (int i = 0; i < menuItems.size(); ++i) { + ItemButtonContainer m = menuItems.get(i); + int p = i / maxRows; + if (p >= page && p < page + maxCols) { + m.setX(xpos[p - page]).setDirty(true); + m.setEnabled(true); + } else if (m.isVisible()) { + m.setEnabled(false); + } + } + } + + currentPage = page; + lblPageNum.setText("Page " + (currentPage + 1) + " of " + numPages); + lblPageNum.setDirty(true); + + if (currentPage == 0) { + if (btnScrollLeft.isVisible()) { + btnScrollLeft.setEnabled(false).setVisible(false).setDirty(true); + } + } else if (!btnScrollLeft.isVisible()) { + btnScrollLeft.setEnabled(true).setVisible(true).setDirty(true); + } + if (currentPage + 1 >= numPages) {//((menuItems.size() / maxRows) - (maxCols - 1))) { + if (btnScrollRight.isVisible()) { + btnScrollRight.setEnabled(false).setVisible(false).setDirty(true); + } + } else if (!btnScrollRight.isVisible()) { + btnScrollRight.setEnabled(true).setVisible(true).setDirty(true); + } + } + + protected synchronized void showCatPage(int page) { + int pageStart = page * tabPageSize; + if (pageStart > tabButtons.size()) { + throw new IllegalArgumentException("Illegal tab page number: " + page); + } + int p = 0; + for (GenericButton b : tabButtons) { + if (p >= pageStart && p < pageStart + tabPageSize) { + b.setEnabled(true).setVisible(true).setDirty(true); + } else if (b.isVisible()) { + b.setEnabled(false).setVisible(false).setDirty(true); + } + ++p; + } + if (page * tabPageSize >= tabButtons.size()) { + btnTabRight.setEnabled(false).setVisible(false).setDirty(true); + } else if (!btnTabRight.isVisible()) { + btnTabRight.setEnabled(true).setVisible(true).setDirty(true); + } + + if (page <= 0) { + btnTabLeft.setEnabled(false).setVisible(false).setDirty(true); + } else { + btnTabLeft.setEnabled(true).setVisible(true).setDirty(true); + } + tabPage = page; + } + + public void buttonPress(Button btn) { + if (btn == btnExit) { + if (aboutActive) { + popup.removeWidgets(BetterShop.getPlugin()); + show(); + aboutActive = false; + } else { + closePopup(player); + } + } else if (btn == btnAbout) { + showAbout(); + } else if (btn == btnScrollLeft) { + if (useScroll) { + itemScroll.setSliderPosition((float) (currentPage - 1) / numPages).setDirty(true); + sliderChanged(itemScroll); + } else { + showPage(currentPage - 1); + } + } else if (btn == btnScrollRight) { + if (useScroll) { + itemScroll.setSliderPosition((float) (currentPage + 1.2) / numPages).setDirty(true); + sliderChanged(itemScroll); + } else { + showPage(currentPage + 1); + } + } else if (btn == itemDetail.btnUp) { + itemDetail.buttonUpPressed(1); + } else if (btn == itemDetail.btnDown) { + itemDetail.buttonDownPressed(1); + } else if (btn == itemDetail.btnUp5) { + itemDetail.buttonUpPressed(5); + } else if (btn == itemDetail.btnDown5) { + itemDetail.buttonDownPressed(5); + } else if (btn == itemDetail.btnUp20) { + itemDetail.buttonUpPressed(20); + } else if (btn == itemDetail.btnDown20) { + itemDetail.buttonDownPressed(20); + } else if (btn == itemDetail.btnBuy) { + if (itemDetail.buyAmt() > 0) { + player.performCommand("shopbuy " + itemDetail.itemIDD() + " " + itemDetail.buyAmt()); +// BetterShop.getPlugin().onCommand(player, +// new PluginCommand("shopbuy", BetterShop.getPlugin()), "shopbuy", new String[]{itemDetail.itemIDD(), String.valueOf(itemDetail.buyAmt())}); + closePopup(player); + } + } else if (btn == itemDetail.btnSell) { + if (itemDetail.sellAmt() > 0) { + player.performCommand("shopsell " + itemDetail.itemIDD() + " " + itemDetail.sellAmt()); + closePopup(player); + } + } else if (btn == btnCatCycle) { + showCategory(catNum + 1); + } else if (btn == btnTabLeft) { + showCatPage(tabPage - 1); + } else if (btn == btnTabRight) { + showCatPage(tabPage + 1); + } else { + int cat = 0; + for (GenericButton b : tabButtons) { + if (btn == b) { + showCategory(cat); + return; + } + ++cat; + } + for (ItemButtonContainer m : menuItems) { + if (btn == m.getButton()) { + itemDetail.updateItem(m.getID(), m.getData()); + return; + } + } + } + } + + public void sliderChanged(Slider scrollbar) { + if (scrollbar == itemScroll) { + itemScroll.savePos(); + //int page = (int) Math.ceil((((float) menuItems.size() / maxRows) - (maxCols - 1)) * scrollbar.getSliderPosition()) - 1; + int page = (int) Math.ceil(numPages * scrollbar.getSliderPosition()) - 1; + if (page < 0) { + page = 0; + } + if (currentPage != page) { + showPage(page); + } + } + } + + public void textChanged(TextFieldChangeEvent event) { + TextField t = event.getTextField(); + if (t == itemDetail.txtAmt) { + itemDetail.amtChanged(event.getOldText(), event.getNewText()); + } + } + + public SpoutPlayer getPlayer() { + return player; + } +} // end class SpoutPopupDisplay + diff --git a/src/me/jascotty2/bettershop/spout/SpoutPopupListener.java b/src/me/jascotty2/bettershop/spout/SpoutPopupListener.java new file mode 100644 index 0000000..1370e0b --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/SpoutPopupListener.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for handling spout gui events + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.spout; + +import me.jascotty2.bettershop.utils.BetterShopLogger; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.getspout.spoutapi.event.screen.ButtonClickEvent; +import org.getspout.spoutapi.event.screen.SliderDragEvent; +import org.getspout.spoutapi.event.screen.TextFieldChangeEvent; + +public class SpoutPopupListener implements Listener { + + @EventHandler + public void onButtonClick(ButtonClickEvent event) { + try{ + SpoutPopupDisplay d = SpoutPopupDisplay.getPopup(event.getPlayer()); + if (d != null && event.getButton().isEnabled()) { + d.buttonPress(event.getButton()); + } + } catch (Exception e) { + BetterShopLogger.Severe("Unexpected error in PopupListener", e); + } + } + + @EventHandler + public void onSliderDrag(SliderDragEvent event) { + try { + SpoutPopupDisplay d = SpoutPopupDisplay.getPopup(event.getPlayer()); + if (d != null) { + d.sliderChanged(event.getSlider()); + } + } catch (Exception e) { + BetterShopLogger.Severe("Unexpected error in PopupListener", e); + } + } + + @EventHandler + public void onTextFieldChange(TextFieldChangeEvent event){ + try { + SpoutPopupDisplay d = SpoutPopupDisplay.getPopup(event.getPlayer()); + if (d != null) { + d.textChanged(event); + } + } catch (Exception e) { + BetterShopLogger.Severe("Unexpected error in PopupListener", e); + } + } +} // end class SpoutPopupListener + diff --git a/src/me/jascotty2/bettershop/spout/gui/ItemButtonContainer.java b/src/me/jascotty2/bettershop/spout/gui/ItemButtonContainer.java new file mode 100644 index 0000000..cd5a52b --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/gui/ItemButtonContainer.java @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: generic container with a button and itemWidget + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.spout.gui; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import org.getspout.spoutapi.gui.Container; +import org.getspout.spoutapi.gui.GenericContainer; +import org.getspout.spoutapi.gui.GenericButton; +import org.getspout.spoutapi.gui.GenericItemWidget; +import org.getspout.spoutapi.gui.RenderPriority; +import org.getspout.spoutapi.gui.Widget; + +public abstract class ItemButtonContainer extends GenericContainer { + + final GenericButton marketButton = new GenericButton(); + final GenericItemWidget picItem = new GenericItemWidget(); + final int itemId; + final byte itemData; + final JItem item; + final String itemName; + + public ItemButtonContainer(int id, byte dat){ + itemId = id; + itemData = dat; + item = JItemDB.GetItem(id, dat); + marketButton.setPriority(RenderPriority.High); + if (item != null && item.IsValidItem()) { + picItem.setTypeId(id).setData(dat); + } else { + picItem.setTypeId(1).setVisible(false).setDirty(true); + } + itemName = item != null ? item.Name() : String.valueOf(id) + (dat != 0 ? ":" + dat : ""); + this.children.add(marketButton); + this.children.add(picItem); +// for (Widget child : children) { +// child.setContainer(this); +// } + } + + public GenericButton getButton(){ + return marketButton; + } + + public int getID(){ + return itemId; + } + + public byte getData(){ + return itemData; + } + + public Widget setEnabled(boolean enable) { + if (marketButton.isVisible() != enable) { + marketButton.setEnabled(enable).setDirty(true); + //((Widget)marketButton).setPriority(enable ? RenderPriority.Low : RenderPriority.High); + setVisible(enable); + } + return this; + } + + @Override + public Container setVisible(boolean enable) { + //if(this.isVisible() != enable) { + marketButton.setVisible(enable).setDirty(true); + if (item != null && item.IsValidItem()) { + picItem.setVisible(enable).setDirty(true); + } + //} + return this; + } + +} // end class ItemButton \ No newline at end of file diff --git a/src/me/jascotty2/bettershop/spout/gui/LargeMarketMenuItem.java b/src/me/jascotty2/bettershop/spout/gui/LargeMarketMenuItem.java new file mode 100644 index 0000000..072ba98 --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/gui/LargeMarketMenuItem.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: large button menu, with the icon on the left & button on right + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.spout.gui; + +public class LargeMarketMenuItem extends ItemButtonContainer { + + public static int DEF_WIDTH = 125, DEF_HEIGHT = 17; + + public LargeMarketMenuItem(int id) { + this(id, (byte) 0); + } + + public LargeMarketMenuItem(int id, byte dat) { + super(id, dat); + this.setWidth(DEF_WIDTH).setHeight(DEF_HEIGHT);//.setFixed(true).setAnchor(WidgetAnchor.CENTER_LEFT); + marketButton.setHeight(height).setWidth(width - 19); + marketButton.setText(itemName).setX(19).setY(0); + picItem.setWidth(8).setHeight(8).setY(1); + } + +} // end class LargeMenuMarketItem diff --git a/src/me/jascotty2/bettershop/spout/gui/MarketItemDetail.java b/src/me/jascotty2/bettershop/spout/gui/MarketItemDetail.java new file mode 100644 index 0000000..e2d71c2 --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/gui/MarketItemDetail.java @@ -0,0 +1,309 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( TODO ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.spout.gui; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import me.jascotty2.bettershop.BSEcon; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.shop.Shop; +import me.jascotty2.bettershop.spout.SpoutPopupDisplay; +import me.jascotty2.bettershop.utils.BetterShopLogger; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.io.CheckInput; +import org.bukkit.entity.Player; +import org.getspout.spoutapi.gui.Color; +import org.getspout.spoutapi.gui.Container; +import org.getspout.spoutapi.gui.GenericButton; +import org.getspout.spoutapi.gui.GenericContainer; +import org.getspout.spoutapi.gui.GenericItemWidget; +import org.getspout.spoutapi.gui.GenericLabel; +import org.getspout.spoutapi.gui.GenericTextField; +import org.getspout.spoutapi.gui.RenderPriority; +import org.getspout.spoutapi.player.SpoutPlayer; + +public class MarketItemDetail extends GenericContainer { + + protected static final int MAX_WIDTH = 427, MAX_HEIGHT = 240; + + int itemId; + byte itemData; + JItem item = null; + //PriceListItem price; + double buyPrice, sellPrice; + long stock; + Player player; + GenericItemWidget picItem = new GenericItemWidget(); + GenericLabel lblName = new GenericLabel(); + GenericLabel lblCash = new GenericLabel(), + lblBuy = new GenericLabel(), + lblSell = new GenericLabel(); + GenericLabel lblAmt = new GenericLabel(); + public final GenericTextField txtAmt = new GenericTextField(); + public final GenericButton btnUp = new GenericButton(), + btnDown = new GenericButton(), + btnUp5 = new GenericButton(), + btnDown5 = new GenericButton(), + btnUp20 = new GenericButton(), + btnDown20 = new GenericButton(); + int currentAmt = 1, maxBuyAmt, maxSellAmt; + public final GenericButton btnBuy = new GenericButton(), + btnSell = new GenericButton(); + GenericLabel lblBuyBtn = new GenericLabel(), + lblSellBtn = new GenericLabel(); + + final Shop displayShop; + + public MarketItemDetail(Player pl) { + player = pl; + displayShop = BetterShop.getShop(player); + this.setWidth(300).setHeight(70); + + picItem.setDepth(15).setWidth(15).setHeight(15).setX(10).setY(14); + picItem.setTypeId(1).setVisible(false); + lblName.setWidth(100).setHeight(20).setX(38).setY(3); + + lblBuy.setWidth(100).setHeight(10).setX(150).setY(3); + lblSell.setWidth(100).setHeight(10).setX(150).setY(18); + + lblCash.setWidth(110).setHeight(20).setX(MAX_WIDTH - 110).setY(10); + updateCash(); + + txtAmt.setX(8).setY(height - 15).setWidth(30).setHeight(12); + txtAmt.setFieldColor(new Color(80 / 255F, 80 / 255F, 80 / 255F)); + txtAmt.setBorderColor(new Color(.8F, .8F, .8F)); + txtAmt.setColor(new Color(.9F, .9F, .9F)); + + lblAmt.setText("Amount: ").setWidth(55).setHeight(10).setX(3).setY(height - 26); + + btnUp.setText("+").setX(42).setY(height - 20).setWidth(12).setHeight(9); + btnDown.setText("-").setX(42).setY(height - 10).setWidth(12).setHeight(9); + btnUp5.setText("+5").setX(btnUp.getX() + 15).setY(btnUp.getY()).setWidth(20).setHeight(9); + btnDown5.setText("-5").setX(btnDown.getX() + 15).setY(btnDown.getY()).setWidth(20).setHeight(9); + btnUp20.setText("+20").setX(btnUp5.getX() + 22).setY(btnUp.getY()).setWidth(22).setHeight(9); + btnDown20.setText("-20").setX(btnDown5.getX() + 22).setY(btnDown.getY()).setWidth(22).setHeight(9); + + btnBuy.setText("").setX(120).setY(height - 30).setWidth(110).setHeight(28); + btnSell.setText("").setX(245).setY(height - 30).setWidth(110).setHeight(28); + + lblBuyBtn.setText("(Buy)").setX(btnBuy.getX() + 5).setY(btnBuy.getY() + 5).setWidth(btnBuy.getWidth() - 10).setHeight(btnBuy.getHeight() - 10).setPriority(RenderPriority.Low); + lblSellBtn.setText("(Sell)").setX(btnSell.getX() + 5).setY(btnSell.getY() + 5).setWidth(btnSell.getWidth() - 10).setHeight(btnSell.getHeight() - 10).setPriority(RenderPriority.Lowest); + + setVisible(false); + + this.children.add(picItem); + this.children.add(lblName); + this.children.add(lblBuy); + this.children.add(lblSell); + this.children.add(lblCash); + this.children.add(txtAmt); + this.children.add(lblAmt); + this.children.add(btnUp); + this.children.add(btnDown); + this.children.add(btnUp5); + this.children.add(btnDown5); + this.children.add(btnUp20); + this.children.add(btnDown20); + this.children.add(btnBuy); + this.children.add(btnSell); + this.children.add(lblBuyBtn); + this.children.add(lblSellBtn); + } + + @Override + public final Container setVisible(boolean vis){ + + if (item != null && item.IsValidItem()) { + picItem.setVisible(vis); + } + + lblAmt.setVisible(vis); + txtAmt.setVisible(vis); + + lblName.setVisible(vis); + + lblBuy.setVisible(vis); + lblSell.setVisible(vis); + + btnUp.setVisible(vis); + btnDown.setVisible(vis); + btnUp5.setVisible(vis); + btnDown5.setVisible(vis); + btnUp20.setVisible(vis); + btnDown20.setVisible(vis); + + btnBuy.setVisible(vis); + btnSell.setVisible(vis); + + lblBuyBtn.setVisible(vis); + lblSellBtn.setVisible(vis); + + this.setDirty(true); + return this; + } + + public final void updateCash() { + lblCash.setText(" Cash: \n" + BSEcon.format(BSEcon.getBalance(player))).setDirty(true); + updateItem(); + } + + public final void updateItem() { + if (itemId > 0) { + updateItem(itemId, itemData); + } + } + + public final void updateItem(int id, byte dat) { + if(!this.isVisible()){ + this.setVisible(true).setDirty(true); + } + itemId = id; + itemData = dat; + //price = null; + buyPrice = sellPrice = -1; + + item = JItemDB.GetItem(id, dat); + if (item != null && item.IsValidItem()) { + picItem.setTypeId(id).setData(dat).setVisible(true); + } else { + picItem.setTypeId(1).setVisible(false); + } + picItem.setDirty(true); + + lblName.setText(item != null ? item.Name() : String.valueOf(id)).setDirty(true); + + try { + buyPrice = displayShop.pricelist.itemBuyPrice(player, id, dat, 1); + sellPrice = displayShop.pricelist.itemSellPrice(player, id, dat, 1); + stock = displayShop.stock.getItemAmount(id, dat); + maxBuyAmt = displayShop.pricelist.getAmountCanBuy(player, item); + maxSellAmt = BSutils.amtHas(player, item); + if (BetterShop.getSettings().useItemStock) { + lblName.setText(lblName.getText() + "\n\n" + (stock < 0 ? "INF" : stock) + " in Stock"); + } + + lblBuy.setText("Buy Price: " + BSEcon.format(buyPrice)).setDirty(true); + lblSell.setText("Sell Price: " + BSEcon.format(sellPrice)).setDirty(true); + + if (!lblAmt.isVisible()) { + setVisible(true); + } + if (currentAmt > maxBuyAmt && currentAmt > maxSellAmt) { + currentAmt = maxBuyAmt > maxSellAmt ? maxBuyAmt : maxSellAmt; + } + setAmt(currentAmt);//maxBuyAmt == 0 ? (maxSellAmt == 0 ? 0 : 1) : 1); + } catch (Exception ex) { + BetterShopLogger.Log(Level.SEVERE, ex); + SpoutPopupDisplay.closePopup((SpoutPlayer) player); + } + } + + public void buttonUpPressed(int d) { + currentAmt += d; + if (currentAmt > maxBuyAmt && currentAmt > maxSellAmt) { + currentAmt = maxBuyAmt > maxSellAmt ? maxBuyAmt : maxSellAmt; + } + setAmt(currentAmt); + } + + public void buttonDownPressed(int d) { + currentAmt -= d; + if (currentAmt < 0) { + currentAmt = (maxBuyAmt == 0 && maxSellAmt == 0) ? 0 : 1; + } + setAmt(currentAmt); + } + + public int buyAmt() { + return currentAmt >= maxBuyAmt ? maxBuyAmt : currentAmt; + } + + public int sellAmt() { + return currentAmt >= maxSellAmt ? maxSellAmt : currentAmt; + } + + public String itemIDD() { + return String.valueOf(itemId) + (itemData == 0 ? "" : ":" + itemData); + } + + public void amtChanged(String from, String to) { + int min = maxBuyAmt == 0 ? (maxSellAmt == 0 ? 0 : 1) : 1; + if (to.isEmpty()) { + setAmt(min); + //redirtyTxtAmt(); + return; + } + int newAmt = -1; + if (CheckInput.IsInt(to) && (newAmt = CheckInput.GetInt(to, newAmt)) > min) { + if (newAmt > maxBuyAmt) { + setAmt(currentAmt = maxBuyAmt); + redirtyTxtAmt(); + } else { + setAmt(newAmt); + } + } else { + setAmt(currentAmt); + redirtyTxtAmt(); + } + } + + protected void setAmt(int amt) { + txtAmt.setText(String.valueOf(currentAmt = amt)).setDirty(true); + txtAmt.setCursorPosition(txtAmt.getText().length()); + +// if (btnUp.isEnabled() && currentAmt >= maxBuyAmt && currentAmt >= maxSellAmt) { +// btnUp.setEnabled(false).setDirty(true); +// } else if (!btnUp.isEnabled() && maxBuyAmt > 0 && maxSellAmt > 0) { +// btnUp.setEnabled(true).setDirty(true); +// } +// +// if (btnDown.isEnabled() && currentAmt <= 1 +// && maxBuyAmt <= currentAmt && maxSellAmt <= currentAmt) { +// btnDown.setEnabled(false).setDirty(true); +// } else if (!btnDown.isEnabled() +// && (currentAmt > 1 || (currentAmt > maxBuyAmt && currentAmt > maxSellAmt))) { +// btnDown.setEnabled(true).setDirty(true); +// } + + lblBuyBtn.setText("Buy " + buyAmt() + " for\n " + + BSEcon.format(buyPrice * buyAmt())).setDirty(true); + lblSellBtn.setText("Sell " + sellAmt() + " for\n " + + BSEcon.format(sellPrice * sellAmt())).setDirty(true); + } + Timer t = null; + + private void redirtyTxtAmt() { + if (t != null) { + t.cancel(); + t = null; + } + t = new Timer(); + t.schedule(new TimerTask() { + + @Override + public void run() { + setAmt(currentAmt); + t = null; + } + }, 1000); + } +} // end class MarketItemDetail diff --git a/src/me/jascotty2/bettershop/spout/gui/SmallMarketMenuItem.java b/src/me/jascotty2/bettershop/spout/gui/SmallMarketMenuItem.java new file mode 100644 index 0000000..65ae249 --- /dev/null +++ b/src/me/jascotty2/bettershop/spout/gui/SmallMarketMenuItem.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: small button menu, with the icon overlaying the button + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.bettershop.spout.gui; + +public class SmallMarketMenuItem extends ItemButtonContainer { + + public static int DEF_WIDTH = 20, DEF_HEIGHT = 20; + + public SmallMarketMenuItem(int id) { + this(id, (byte) 0); + } + + public SmallMarketMenuItem(int id, byte dat) { + super(id, dat); + this.setWidth(DEF_WIDTH).setHeight(DEF_HEIGHT);//.setFixed(true).setAnchor(WidgetAnchor.CENTER_LEFT); + marketButton.setHeight(height).setWidth(width); + marketButton.setTooltip(itemName); + picItem.setWidth(8).setHeight(8).setX(2).setY(2); + } + +} // end class SmallMarketMenuItem diff --git a/src/me/jascotty2/bettershop/utils/BSPermissions.java b/src/me/jascotty2/bettershop/utils/BSPermissions.java new file mode 100644 index 0000000..85d9753 --- /dev/null +++ b/src/me/jascotty2/bettershop/utils/BSPermissions.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for handling permissions checks + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.utils; + +import com.nijikokun.bukkit.Permissions.Permissions; +import me.jascotty2.bettershop.BSutils; +import me.jascotty2.bettershop.BetterShop; +import me.jascotty2.bettershop.enums.BetterShopPermission; +import me.jascotty2.lib.util.Str; +import net.milkbowl.vault.permission.Permission; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * @author jacob + */ +public class BSPermissions { + + public static Permissions permissionsPlugin = null; + public static Permission vaultPerms = null; + + public static boolean hasPermission(CommandSender player, BetterShopPermission node) { + return hasPermission(player, node.toString(), false); + } + + public static boolean hasPermission(CommandSender player, BetterShopPermission node, boolean notify) { + return hasPermission(player, node.toString(), notify); + } + + public static boolean hasPermission(CommandSender player, String node) { + return hasPermission(player, node, false); + } + + public static boolean hasPermission(CommandSender player, String node, boolean notify) { + if (player == null || player.isOp() || !(player instanceof Player) + || node == null || node.length() == 0) { // ops override permission check (double-check is a Player) + return true; + } + if (has((Player) player, node)) { + return true; + } else if (notify) { + //PermDeny(player, node); + BSutils.sendMessage(player, + BetterShop.getSettings().getString("permdeny").replace("", node)); + } + return false; + } + + public static boolean has(Player player, String node) { + try { + if (vaultPerms != null) { + return vaultPerms.has(player, node); + } else if (permissionsPlugin != null) { + return permissionsPlugin.getHandler().has(player, node); + } +// System.out.println("no perm: checking superperm for " + player.getName() + ": " + node); +// System.out.println(player.hasPermission(node)); +// for(PermissionAttachmentInfo i : player.getEffectivePermissions()){ +// System.out.println(i.getPermission()); +// } + if (player.hasPermission(node)) { + return true; + } else if (!node.contains("*") && Str.count(node, '.') >= 2) { +// System.out.println("Checking for " + node.substring(0, node.lastIndexOf('.') + 1) + "* : " +// + player.hasPermission(node.substring(0, node.lastIndexOf('.') + 1) + "*")); + return player.hasPermission(node.substring(0, node.lastIndexOf('.') + 1) + "*"); + } +// System.out.println("no permission"); + return false; + } catch (Exception e) { + BetterShopLogger.Severe(e, false); + } + return node.length() < 16 // if invalid node, assume true + || (!node.substring(0, 16).equalsIgnoreCase("BetterShop.admin") // only ops have access to .admin + && !node.substring(0, 19).equalsIgnoreCase("BetterShop.discount")); + } + +} // end class BSPermissions diff --git a/src/me/jascotty2/bettershop/utils/BetterShopLogger.java b/src/me/jascotty2/bettershop/utils/BetterShopLogger.java new file mode 100644 index 0000000..4e1d6b1 --- /dev/null +++ b/src/me/jascotty2/bettershop/utils/BetterShopLogger.java @@ -0,0 +1,268 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: plugin logging + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.bettershop.utils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.bettershop.BSConfig; +import me.jascotty2.bettershop.BetterShop; + +/** + * @author jacob + */ +public class BetterShopLogger extends Logger { + + private static Logger _logger = Logger.getLogger("Minecraft"); + protected final static Logger logger = new BetterShopLogger(); + protected static String logFormat = "[BetterShop] %s"; + public FileWriter commlog_fstream = null; + public BufferedWriter commlog_out = null; + + BetterShopLogger() { + //super("Minecraft", null); + super(null, null); + } + + public static void init(Logger customLogger) { + if (customLogger != null) { + _logger = customLogger; + } + } + + public static void setUsePrefix(boolean b) { + logFormat = b ? "[BetterShop] %s" : "%s"; + } + /* + public class BetterShopLogger extends CustomLogger { + + protected final static Logger logger = new BetterShopLogger(); + public FileWriter commlog_fstream = null; + public BufferedWriter commlog_out = null; + + BetterShopLogger() { + super("BetterShop", "Minecraft"); + }*/ + + @Override + public synchronized void log(Level level, String message, Object param) { + //super.log(level, message == null ? null : String.format(logFormat, message), param); + //_logger.log(level, message == null ? null : String.format(logFormat, message), param); + Log(level, message, param); + } + + @Override + public synchronized void log(Level level, String message, Object[] params) { + //super.log(level, message == null ? null : String.format(logFormat, message), params); + //_logger.log(level, message == null ? null : String.format(logFormat, message), params); + Log(level, message, params); + } + + @Override + public synchronized void log(Level level, String message, Throwable thrown) { + //super.log(level, message == null ? null : String.format(logFormat, message), thrown); + //_logger.log(level, message == null ? null : String.format(logFormat, message), thrown); + Log(level, message, thrown); + } + + @Override + public synchronized void severe(String msg) { + Log(Level.SEVERE, msg, null); + } + + @Override + public synchronized void warning(String msg) { + Log(Level.WARNING, msg, null); + } + + @Override + public synchronized void info(String msg) { + Log(Level.INFO, msg, null); + } + + @Override + public synchronized void config(String msg) { + _logger.config(msg); + } + + @Override + public synchronized void fine(String msg) { + Log(Level.FINE, msg, null); + } + + @Override + public synchronized void finer(String msg) { + Log(Level.FINER, msg, null); + } + + @Override + public synchronized void finest(String msg) { + Log(Level.FINEST, msg, null); + } + + public static Logger getLogger() { + return logger; + } + + public static void Fine(String msg) { + Log(Level.FINE, msg, null); + } + + public static void Info(String msg) { + Log(Level.INFO, msg, (Throwable) null); + } + + public static void Warning(String msg) { + Log(Level.WARNING, msg, null); + } + + public static void Warning(Exception err) { + Log(Level.WARNING, null, err); + } + + public static void Warning(String msg, Throwable err) { + Log(Level.WARNING, msg, err); + } + + + public static void Severe(String msg) { + Log(Level.SEVERE, msg, null); + } + + public static void Severe(Exception err) { + Log(Level.SEVERE, null, err); + } + + public static void Severe(String msg, Throwable err) { + Log(Level.SEVERE, msg, err); + } + + public static void Log(String msg) { + Log(Level.INFO, msg, null); + } + + public static void Log(String msg, Throwable err) { + Log(Level.INFO, msg, err); + } + + public static void Log(Level loglevel, String msg) { + Log(loglevel, msg, null); + } + + public static void Log(Level loglevel, Exception err) { + Log(loglevel, null, err); + } + + public static void Log(Level loglevel, Throwable err) { + Log(loglevel, null, err); + } + + public static void Log(Level loglevel, String msg, Object params) { + if (params != null && params instanceof Throwable) { + Throwable err = (Throwable) params; + if (msg == null) { + _logger.log(loglevel, String.format(logFormat, + err == null ? "? unknown exception ?" : err.getMessage()), err); + } else { + _logger.log(loglevel, String.format(logFormat, msg), err); + } + } else if (msg == null) { + _logger.log(loglevel, String.format(logFormat), params); + } else { + _logger.log(loglevel, String.format(logFormat, msg), params); + } + } + + public static void Log(Level loglevel, String msg, Object[] params) { + if (msg == null) { + _logger.log(loglevel, String.format(logFormat), params); + } else { + _logger.log(loglevel, String.format(logFormat, msg), params); + } + } + + public static void LogCommand(String playername, String command) { + ((BetterShopLogger) logger).logCommand(playername, command); + } + + public static void CloseCommandLog() { + ((BetterShopLogger) logger).closeCommandLog(); + } + + public void logCommand(String playername, String command) { + if (commlog_fstream == null) { + try { + commlog_fstream = new FileWriter(new File(BSConfig.pluginFolder, + BetterShop.getSettings().commandFilename), true); + commlog_out = new BufferedWriter(commlog_fstream); + } catch (IOException ex) { + Log(Level.SEVERE, "Failed to open logfile for writing", ex); + commlog_fstream = null; + commlog_out = null; + return; + } + } + try { + commlog_out.write(commandLogStr(playername, command)); + commlog_out.newLine(); + } catch (IOException ex) { + BetterShopLogger.Log(Level.SEVERE, "Failed to write to logfile", ex); + } + } + + public void closeCommandLog() { + if (commlog_fstream != null) { + try { + commlog_out.flush(); + commlog_out.close(); + commlog_fstream.flush(); + commlog_fstream.close(); + } catch (IOException ex) { + // BetterShopLogger.Log(Level.SEVERE, "Failed to write to logfile", ex, false); + } + commlog_out = null; + commlog_fstream = null; + } + } + + static String commandLogStr(String playername, String command) { + String time[] = (new java.text.SimpleDateFormat("kk:hh:mm:ss:a:z:Z:yyyy:MM:ww:DD:dd:EEE")).format(new java.util.Date()).split(":"); + return BetterShop.getSettings().getString("logformat"). + replace("", time[0]). + replace("", time[1]). + replace("", time[2]). + replace("", time[3]). + replace("
", time[4]). + replace("", time[5]). + replace("", time[6]). + replace("", time[7]). + replace("", time[8]). + replace("", time[9]). + replace("", time[10]). + replace("", time[11]). + replace("", time[12]). + replace("", "\t"). + replace("", String.valueOf((int) (System.currentTimeMillis() / 1000))). + replace("", playername).replace("", playername). + replace("", command).replace("", command); + } +} // end class BetterShopLogger + diff --git a/src/me/jascotty2/lib/bukkit/MinecraftChatStr.java b/src/me/jascotty2/lib/bukkit/MinecraftChatStr.java new file mode 100644 index 0000000..c04ee06 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/MinecraftChatStr.java @@ -0,0 +1,630 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for working with & formatting strings in minecraft chat + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit; + +import me.jascotty2.lib.util.Str; +import java.util.LinkedList; +import org.bukkit.ChatColor; + +public class MinecraftChatStr { + + public final static int chatwidth = 318; // 325 + public static String charWidthIndexIndex = + " !\"#$%&'()*+,-./" + + "0123456789:;<=>?" + + "@ABCDEFGHIJKLMNO" + + "PQRSTUVWXYZ[\\]^_" + + "'abcdefghijklmno" + + "pqrstuvwxyz{|}~⌂" + + "ÇüéâäàåçêëèïîìÄÅ" + + "ÉæÆôöòûùÿÖÜø£Ø×ƒ" + + "áíóúñѪº¿®¬½¼¡«»"; + public static int[] charWidths = { + 4, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6, + 3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6, + 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, + 6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6, + // not sure what tkelly made these rows for.. + 8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9, + 8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7, + 7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1}; + // chat limmitation: repetitions of characters is limmited to 119 per line + // so: repeating !'s will not fill a line + + public static int getStringWidth(String s) { + int i = 0; + if (s != null) { + s = s.replaceAll("\\u00A7.", ""); + for (int j = 0; j < s.length(); j++) { + if (s.charAt(j) >= 0) { + i += getCharWidth(s.charAt(j)); + } + } + } + return i; + } + + public static int getCharWidth(char c) { + //return getCharWidth(c, 0); + int k = charWidthIndexIndex.indexOf(c); + if (c != '\247' && k >= 0) { + return charWidths[k]; + } + return 0; + } + + public static int getCharWidth(char c, int defaultReturn) { + int k = charWidthIndexIndex.indexOf(c); + if (c != '\247' && k >= 0) { + return charWidths[k]; + } + return defaultReturn; + } + + public static String uncoloredStr(String s) { + //return s != null ? s.replaceAll("\\u00A7.", "") : s; + //s.replaceAll("\\\u00A7.", "") + return s == null ? null : ChatColor.stripColor(s); + } + + /** + * pads str on the right with spaces (left-align) + * @param str string to format + * @param len spaces to pad + * @return str with padding appended + */ + public static String padRight(String str, int len) { + return padRight(str, len, ' '); + } + + /** + * pads str on the right with pad (left-align) + * @param str string to format + * @param len spaces to pad + * @param pad character to use when padding + * @return str with padding appended + */ + public static String padRight(String str, int len, char pad) { + // for purposes of this function, assuming a normal char to be 6 + len *= 6; + len -= getStringWidth(str); + return str + Str.repeat(pad, len / getCharWidth(pad, 6)); + } + + /** + * pads str on the right to # of pixels + * @param str string to format + * @param pad character to use when padding + * @param abslen pixels to space out + * @return + */ + public static String padRight(String str, char pad, int abslen) { + abslen -= getStringWidth(str); + return str + Str.repeat(pad, abslen / getCharWidth(pad, 6)); + } + +// public static String strPadRightChat(String str, int abslen) { +// abslen -= getStringWidth(str); +// return str + Str.repeat(' ', abslen / getCharWidth(' ', 6)); +// } + + public static String padRight(String str, char pad) { + int width = chatwidth - getStringWidth(str); + return str + Str.repeat(pad, width / getCharWidth(pad, 6)); + } + + public static String padRight(String str) { + int width = chatwidth - getStringWidth(str); + return str + Str.repeat(' ', width / getCharWidth(' ', 6)); + } + + /** + * pads str on the left with pad (right-align) + * @param str string to format + * @param len spaces to pad + * @param pad character to use when padding + * @return str with padding prepended + */ + public static String padLeft(String str, int len, char pad) { + // for purposes of this function, assuming a normal char to be 6 + len *= 6; + len -= getStringWidth(str); + return Str.repeat(pad, len / getCharWidth(pad, 6)) + str; + } + + public static String padLeft(String str, char pad, int abslen) { + abslen -= getStringWidth(str); + return Str.repeat(pad, abslen / getCharWidth(pad, 6)).concat(str); + } + +// public static String strPadLeftChat(String str, int abslen) { +// abslen -= getStringWidth(str); +// return Str.repeat(' ', abslen / getCharWidth(' ', 6)).concat(str); +// } + + public static String padLeft(String str, char pad) { + int width = chatwidth - getStringWidth(str); + return Str.repeat(pad, width / getCharWidth(pad, 6)).concat(str); + } + + public static String padLeft(String str) { + int width = chatwidth - getStringWidth(str); + return Str.repeat(' ', width / getCharWidth(' ', 6)).concat(str); + } + + /** + * pads str on the left & right with pad (center-align) + * @param str string to format + * @param len spaces to pad + * @param pad character to use when padding + * @return str centered with pad + */ + public static String padCenter(String str, int len, char pad) { + // for purposes of this function, assuming a normal char to be 6 + len *= 6; + len -= getStringWidth(str); + int padwid = getCharWidth(pad, 6); + int prepad = (len / padwid) / 2; + len -= prepad * padwid; + return Str.repeat(pad, prepad) + str + Str.repeat(pad, len / padwid); + } + + /** + * pads str on the left & right to # of pixels with pad (center-align) + * @param str string to format + * @param pad character to use when padding + * @param abslen pixels to make the result string + * @return + */ + public static String padCenter(String str, char pad, int abslen) { + abslen -= getStringWidth(str); + int padwid = getCharWidth(pad, 6); + int prepad = (abslen / padwid) / 2; + abslen -= prepad * padwid; + return Str.repeat(pad, prepad) + str + Str.repeat(pad, abslen / padwid); + } + + public static String padCenter(String str, char pad) { + int width = chatwidth - getStringWidth(str); + int padwid = getCharWidth(pad, 6); + int prepad = (width / padwid) / 2; + width -= prepad * padwid; + return Str.repeat(pad, prepad) + str + Str.repeat(pad, width / padwid); + } + + public static int strLen(String str) { + return str == null ? -1 : ChatColor.stripColor(str).length(); +// if (!str.contains("\u00A7")) { +// return str.length(); +// } +// // just searching for \u00A7. +// return str.replaceAll("\\u00A7.", "").length(); + } + + public static String strTrim(String str, int length) { + if (uncoloredStr(str).length() > length) { + int width = length; + String ret = ""; + boolean lastCol = false; + for (char c : str.toCharArray()) { + if (c == '\u00A7') { + ret += c; + lastCol = true; + } else { + if (!lastCol) { + if (width - 1 >= 0) { + width -= 1; + ret += c; + } else { + return ret; + } + } else { + ret += c; + lastCol = false; + } + } + } + } + return str; + } + + public static String strChatTrim(String str) { + return strChatTrim(str, chatwidth); + } + + public static String strChatTrim(String str, int absLen) { + int width = getStringWidth(str); + if (width > absLen) { + width = absLen; + String ret = ""; + boolean lastCol = false; + for (char c : str.toCharArray()) { + if (c == '\u00A7') { + ret += c; + lastCol = true; + } else { + if (!lastCol) { + int w = getCharWidth(c); + if (width - w >= 0) { + width -= w; + ret += c; + } else { + return ret; + } + } else { + ret += c; + lastCol = false; + } + } + } + } + return str; + } + + public static String strWordWrap(String str) { + return strWordWrap(str, 0, ' '); + } + + public static String strWordWrap(String str, int tab) { + return strWordWrap(str, tab, ' '); + } + + public static String strWordWrap(String str, int tab, char tabChar) { + String ret = ""; + while (str.length() > 0) { + // find last char of first line + if (getStringWidth(str) <= chatwidth) { + return (ret.length() > 0 ? ret + "\n" + lastStrColor(ret) + Str.repeat(tabChar, tab) : "").concat(str); + } + String line1 = strChatTrim(str); + int lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + } + //ret += strPadRightChat((ret.length() > 0 ? unformattedStrRepeat(tabChar, tab) : "") + str.substring(0, lastPos)); + ret += (ret.length() > 0 ? "\n" + Str.repeat(tabChar, tab) + lastStrColor(ret) : "") + str.substring(0, lastPos); + str = str.substring(lastPos + 1); + } + return ret; + } + + public static String strWordWrapRight(String str, int tab) { + return strWordWrapRight(str, tab, ' '); + } + + /** + * right-aligns paragraphs + * @param str + * @param tab + * @param tabChar + * @return + */ + public static String strWordWrapRight(String str, int tab, char tabChar) { + String ret = ""; + while (str.length() > 0) { + // find last char of first line + if (getStringWidth(str) <= chatwidth) { + return (ret.length() > 0 ? ret + "\n" + lastStrColor(ret) : "").concat(padLeft(str, tabChar)); + } + String line1 = strChatTrim(str); + int lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + } + //ret += strPadLeftChat(str.substring(0, lastPos), tabChar); + ret += (ret.length() > 0 ? "\n" + lastStrColor(ret) : "") + padLeft(str.substring(0, lastPos), tabChar); + str = str.substring(lastPos + 1); + } + return ret; + } + + /** + * will left-align the start of the string until sepChar, then right-align the remaining paragraph + * @param str + * @param tab + * @param tabChar + * @param sepChar + * @return + */ + public static String strWordWrapRight(String str, int tab, char tabChar, char sepChar) { + String ret = ""; + String line1 = strChatTrim(str); + // first run the first left & right align + if (line1.contains("" + sepChar)) { + int lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + int sepPos = line1.indexOf(sepChar) + 1; + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + } else if (sepPos > lastPos) { + lastPos = sepPos; + } + ret += str.substring(0, sepPos); + ret += padLeft(str.substring(sepPos, lastPos), ' ', chatwidth - getStringWidth(ret)); + str = str.substring(lastPos + 1); + } + while (str.length() > 0) { + // find last char of first line + if (getStringWidth(str) <= chatwidth) { + return (ret.length() > 0 ? ret + "\n" + lastStrColor(ret) : "").concat(padLeft(str, tabChar)); + } + line1 = strChatTrim(str); + int lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + } + //ret += strPadLeftChat(str.substring(0, lastPos), tabChar); + ret += (ret.length() > 0 ? "\n" + lastStrColor(ret) : "") + padLeft(str.substring(0, lastPos), tabChar); + str = str.substring(lastPos + 1); + } + return ret; + } + + /** + * will left-align the start of the string until sepChar, then right-align the remaining paragraph + * @param str + * @param width + * @param tab + * @param tabChar + * @param sepChar + * @return + */ + public static String strWordWrapRight(String str, int width, int tab, char tabChar, char sepChar) { + String ret = ""; + String line1 = strTrim(str, width); + // first run the first left & right align + if (line1.contains("" + sepChar)) { + int lastPos = line1.length() - (ret.length() > 0 ? tab + 1 : 1); + int sepPos = line1.indexOf(sepChar) + 1; + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + } else if (sepPos > lastPos) { + lastPos = sepPos; + } + ret += str.substring(0, sepPos); + ret += padLeft(str.substring(sepPos, lastPos), ' ', width - strLen(ret)); + str = str.substring(lastPos + 1); + } + while (str.length() > 0) { + // find last char of first line + if (strLen(str) <= width) { + return (ret.length() > 0 ? ret + "\n" + lastStrColor(ret) : "").concat(Str.padLeft(str, width, tabChar)); + } + line1 = strChatTrim(str); + int lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + } + //ret += strPadLeftChat(str.substring(0, lastPos), tabChar); + ret += (ret.length() > 0 ? "\n" + lastStrColor(ret) : "") + Str.padLeft(str.substring(0, lastPos), width, tabChar); + str = str.substring(lastPos + 1); + } + return ret; + } + + public static String lastStrColor(String str) { + int i = str.lastIndexOf('\u00A7'); + if (i >= 0 && i + 1 < str.length()) { + return str.substring(i, i + 2); + } + return "\u00A7F";//white + } + + private static boolean containsAlignTag(String str, String tag) { + int pos = str.indexOf("<" + tag); + if (pos >= 0) { + return str.length() > pos + ("<" + tag).length() + && (str.charAt(pos + ("<" + tag).length()) == '>' + || str.charAt(pos + ("<" + tag).length() + 1) == '>'); + } + return false; + } + + private static boolean containsAlignTag(LinkedList input, String tag) { + for (String l : input) { + if (containsAlignTag(l, tag)) { + return true; + } + } + return false; + } + + /** + * UNTESTED: DON'T USE YET + */ + public static String alignTags(String input, boolean minecraftChatFormat) { + for (String fm : new String[]{"l", "r", "c"}) { + while (containsAlignTag(input, fm)) { + char repl = ' '; + if (input.matches("^.*<" + fm + ".>.*$")) { + repl = input.substring(input.indexOf("<" + fm) + 2).charAt(0); + input = input.replaceFirst("<" + fm + ".>", "<" + fm + ">"); + } + + if (fm.equals("l")) { + if (minecraftChatFormat) { + input = padRight(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } else { + input = Str.padRight(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } + } else if (fm.equals("c")) { + if (minecraftChatFormat) { + input = padCenter(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } else { + input = Str.padCenter(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } + } else { + if (minecraftChatFormat) { + input = padLeft(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } else { + input = Str.padLeft(input.substring(0, input.indexOf("<" + fm + ">")), input.indexOf("<" + fm + ">"), repl) + input.substring(input.indexOf("<" + fm + ">") + 3); + } + } + } + } + return input; + } + + public static LinkedList alignTags(LinkedList input, boolean minecraftChatFormat) { + if (input == null || input.size() == 0) { + return input; + } + char[] repl = new char[input.size()]; + for (String fm : new String[]{"l", "r", "c"}) { + while (containsAlignTag(input, fm)) { + for (int i = 0; i < input.size(); ++i) { + if (input.get(i).matches("^.*<" + fm + ".>.*$")) {// || input.get(1).matches("^.*.*$")) { + repl[i] = input.get(i).substring(input.get(i).indexOf("<" + fm) + 2).charAt(0); //, input.get(1).indexOf(">") + input.set(i, input.get(i).replaceFirst("<" + fm + ".>", "<" + fm + ">")); + } else { + repl[i] = ' '; + } + } + int maxPos = 0; + for (int i = 0; i < input.size(); ++i) { + if (input.get(i).indexOf("<" + fm + ">") > maxPos) { + maxPos = input.get(i).indexOf("<" + fm + ">"); + } + } + + LinkedList newinput = new LinkedList(); + for (int i = 0; i < input.size(); ++i) { + String line = input.get(i); + + if (line.indexOf("<" + fm + ">") != -1) { + if (fm.equals("l")) { + if (minecraftChatFormat) { + newinput.add(MinecraftChatStr.padRight(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } else { + newinput.add(Str.padRight(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } + } else if (fm.equals("c")) { + if (minecraftChatFormat) { + newinput.add(MinecraftChatStr.padCenter(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } else { + newinput.add(Str.padCenter(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } + } else { + if (minecraftChatFormat) { + newinput.add(MinecraftChatStr.padLeft(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } else { + newinput.add(Str.padLeft(line.substring(0, line.indexOf("<" + fm + ">")), maxPos, repl[i]) + line.substring(line.indexOf("<" + fm + ">") + 3)); + } + } + } else { + newinput.add(line); + } + } + input = newinput; + } + } + return input; + } + + public static String getChatColorStr(String col, ChatColor def) { + ChatColor c = getChatColor(col); + return c != null ? c.toString() : (def != null ? def.toString() : null); + } + + public static ChatColor getChatColor(String col){ + if (col == null || col.length() == 0) { + return null; + } else if (col.length() >= 2 && col.startsWith("\u00A7")) { + String c = col.substring(1, 2).toLowerCase(); + return ChatColor.getByChar(c); + } + col = col.toLowerCase().trim(); + /* + # &0 is black + # &1 is dark blue + # &2 is dark green + # &3 is dark sky blue + # &4 is red + # &5 is magenta + # &6 is gold or amber + # &7 is light grey + # &8 is dark grey + # &9 is medium blue + # &2 is light green + # &b is cyan + # &c is orange-red + # &d is pink + # &e is yellow + # &f is white + */ + if (col.equalsIgnoreCase("black")) { + return ChatColor.BLACK;//"\u00A70"; //String.format("\u00A7%x", 0x0);// + } else if (col.equals("dark blue")) { + return ChatColor.DARK_BLUE;//"\u00A71"; // String.format("\u00A7%x", 0x1);// + } else if (col.equals("green") || col.equals("dark green")) { + return ChatColor.DARK_GREEN;//"\u00A72"; // String.format("\u00A7%x", 0x2);// + } else if (col.equals("sky blue") || col.equals("dark sky blue") || col.equals("teal")) { + return ChatColor.DARK_AQUA;//"\u00A73"; // String.format("\u00A7%x", 0x3);// + } else if (col.equals("dark red")) { + return ChatColor.DARK_RED;//"\u00A74"; // String.format("\u00A7%x", 0x4);// + } else if (col.equals("magenta") || col.equals("purple")) { + return ChatColor.DARK_PURPLE;//"\u00A75"; // String.format("\u00A7%x", 0x5);// + } else if (col.equals("gold") || col.equals("amber") || col.equals("dark yellow")) { + return ChatColor.GOLD;//"\u00A76"; // String.format("\u00A7%x", 0x6);// + } else if (col.equals("light gray") || col.equals("light grey")) { + return ChatColor.GRAY;//"\u00A77"; // String.format("\u00A7%x", 0x7);// + } else if (col.equals("dark gray") || col.equals("dark grey") || col.equals("gray") || col.equals("grey")) { + return ChatColor.DARK_GRAY;//"\u00A78"; // String.format("\u00A7%x", 0x8);// + } else if (col.equals("blue") || col.equals("medium blue")) { + return ChatColor.BLUE;//"\u00A79"; // String.format("\u00A7%x", 0x9);// + } else if (col.equals("light green") || col.equals("lime") || col.equals("lime green")) { + return ChatColor.GREEN;//"\u00A7a"; // String.format("\u00A7%x", 0xA);// + } else if (col.equals("aqua") || col.equals("cyan") || col.equals("light blue")) { + return ChatColor.AQUA;//"\u00A7b"; // String.format("\u00A7%x", 0xB);// + } else if (col.equals("red") || col.equals("orange") || col.equals("orange-red") || col.equals("red-orange")) { + return ChatColor.RED;//"\u00A7c"; // String.format("\u00A7%x", 0xC);// + } else if (col.equals("pink") || col.equals("light red") || col.equals("light purple")) { + return ChatColor.LIGHT_PURPLE;//"\u00A7d"; // String.format("\u00A7%x", 0xD);// + } else if (col.equals("yellow")) { + return ChatColor.YELLOW;//"\u00A7e"; // String.format("\u00A7%x", 0xE);// + } else if (col.equals("white")) { + return ChatColor.WHITE;//"\u00A7f"; //String.format("\u00A7%x", 0xF);// + } else { + return null; + } + } +} diff --git a/src/me/jascotty2/lib/bukkit/ServerInfo.java b/src/me/jascotty2/lib/bukkit/ServerInfo.java new file mode 100644 index 0000000..c7e05ce --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/ServerInfo.java @@ -0,0 +1,298 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for obtaining more info about a bukkit server + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.lib.bukkit; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; + +/** + * @author jacob + */ +public class ServerInfo { + + public static String suid = null, sip = null, sipM = null; + private static final String suidSysProps[] = new String[]{ + "os.name", "os.arch", "java.class.version", "user.dir"}; + + public static String serverUID() { + if (suid == null) { + try { + suid = ""; + for (String p : suidSysProps) { + suid += System.getProperty(p); + } + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + while (nets.hasMoreElements()) { + NetworkInterface nic = nets.nextElement(); + suid += nic.getDisplayName(); + suid += Arrays.toString(nic.getHardwareAddress()); + } +// File f = new File(ServerInfo.class.getProtectionDomain(). +// getCodeSource().getLocation().getPath().replace("%20", " ").replace("%25", "%")); +// suid += f.getParentFile().getAbsolutePath(); + // Get hostname + try { + suid += InetAddress.getLocalHost().getHostName(); + } catch (/*UnknownHost*/Exception e) { + } + try{ + String javaPath = System.getProperty("java.home"); + suid += javaPath; + File java = new File(javaPath); + suid += String.valueOf(java.lastModified()); + }catch(Exception e){ + } + suid = SUIDmd5Str(suid); + } catch (Exception ex) { + try { + //Logger.getAnonymousLogger().log(Level.WARNING, ex.getMessage(), ex); + suid = SUIDmd5Str(serverIPs()); + } catch (Exception ex1) { + suid = shorten(serverIPs().replace(":", "").replace(".", ""), 16); + } + } + } + + return suid; + } + + private static String shorten(String s, int len) { + return len > 0 && s.length() > len ? s.substring(0, len) : s; + } + + public static String SUIDmd5Str(String txt) throws NoSuchAlgorithmException { + if (txt == null) { + txt = "null"; + } + byte hash[] = MessageDigest.getInstance("MD5").digest(txt.getBytes()); + String ret = ""; + char chars[] = {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', + '~', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '-', '+', //',', '.', '=', ':', ';', '/', '?', '!', '@', '#', '$', '%', '^', '&', '*', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M'}; + for (byte b : hash) { + ret += chars[((int) b + 255) % chars.length]; + } + return ret; + } + + public static String serverIPs() { + return serverIPs(false); + } + + public static String serverIPs(int maxLen) { + return shorten(serverIPs(false), maxLen); + } + + public static String serverIPs(boolean mask, int maxLen) { + return shorten(serverIPs(mask), maxLen); + } + + public static String serverIPs(boolean mask) { + if (sip == null) { + sip = ""; + try { + // Obtain the InetAddress of the computer on which this program is running + InetAddress localaddr = InetAddress.getLocalHost(); + sip = localaddr.getHostName(); + try { + URL autoIP = new URL("http://automation.whatismyip.com/n09230945.asp"); + BufferedReader in = new BufferedReader(new InputStreamReader(autoIP.openStream())); + sip += ":" + (in.readLine()).trim(); + + } catch (Exception e) { + sip += ":ukpip"; + } + for (InetAddress i : InetAddress.getAllByName(localaddr.getHostName())) { + if (!i.isLoopbackAddress()) { + sip += ":" + i.getHostAddress(); + } + } + } catch (Exception ex) { + sip += ":ukh"; + } + } + if (mask && sipM == null) { + try { + sipM = SUIDmd5Str(sip); + } catch (Exception ex) { + //Logger.getLogger(ServerInfo.class.getName()).log(Level.SEVERE, null, ex); + sipM = shorten(sip.replace(":", "").replace(".", ""), 16); + } + } + return mask ? sipM : sip; + } + + public static String getBukkitVersion() { + return getBukkitVersion(false); + } + + /** reads the server log for Bukkit Version + * @param includeStart if true, will append a newline a & the last start timestamp + * @return + */ + public static String getBukkitVersion(boolean includeStart) { + File slog = new File("server.log"); + if (slog.exists() && slog.canRead()) { + FileReader fstream = null; + try { + String ver = ""; + fstream = new FileReader(slog.getAbsolutePath()); + BufferedReader in = new BufferedReader(fstream); + + String line = ""; + while ((line = in.readLine()) != null) { + if (line.contains("This server is running Craftbukkit version git-Bukkit-")) { + ver = line; + } + } + if (ver.length() > 0) { + return !includeStart ? ver.substring(ver.indexOf("git-Bukkit-")) + : ver.substring(ver.indexOf("git-Bukkit-")) + "\nStartTime: " + ver.substring(0, 19) + + " (" + serverRunTimeSpan(ver.substring(0, 19)) + ")"; + } else { + return "?"; + } + } catch (Exception ex) { + Logger.getAnonymousLogger().log(Level.SEVERE, ex.getMessage(), ex); + } finally { + try { + fstream.close(); + } catch (IOException ex) { + } + } + + } + return "?"; + } + + public static String getBukkitRunTimeSpan() { + String b = getBukkitVersion(true); + if (b.contains("(")) { + b = b.substring(b.lastIndexOf('(') + 1); + b = b.substring(0, b.length() - 1); + return b; + } + return "?"; + } + + public static String serverRunTimeSpan(String startTime) { + Date uploadDate = null; + try { + // 2011-04-01 21:35:22 + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss"); + uploadDate = formatter.parse(startTime.trim()); + } catch (ParseException ex) { + Logger.getAnonymousLogger().log(Level.SEVERE, "Error parsing log start date", ex); + return ex.getMessage(); + } + long sec = ((new Date()).getTime() - uploadDate.getTime()) / 1000; + int mon = (int) (sec / 2592000); + sec -= mon * 2592000; + + int day = (int) (sec / 86400); + sec -= day * 86400; + + int hr = (int) (sec / 3600); + sec -= hr * 3600; + + int min = (int) (sec / 60); + sec = sec % 60; + + String timeSpan = ""; + if (mon > 0) { + timeSpan += mon + " Months, "; + } + if (day > 0) { + timeSpan += day + " Days, "; + } + if (hr > 0) { + timeSpan += hr + " Hours, "; + } + if (min > 0) { + timeSpan += min + " Minutes, "; + } + return timeSpan + sec + " Sec"; + } + + public static String[] installedPlugins(Server sv) { + ArrayList enabled = new ArrayList(); + for (Plugin p : sv.getPluginManager().getPlugins()) { + if (p.isEnabled()) { + enabled.add((p.getDescription().getName() != null ? p.getDescription().getName() : p.toString()) + + (p.getDescription().getVersion() != null ? " v" + p.getDescription().getVersion() : "")); + } + } + return enabled.toArray(new String[0]); + } + + public static String installedPluginsStr(Server sv) { + String allplugins[] = ServerInfo.installedPlugins(sv), + plugins = ""; + for (int i = 0; i < allplugins.length; ++i) { + plugins += allplugins[i]; + if (i + 1 < allplugins.length) { + plugins += ", "; + } + } + return plugins; + } + + public static String[] serverWorldNames(Server sv) { + ArrayList enabled = new ArrayList(); + for (World w : sv.getWorlds()) { + enabled.add(w.getName());// + (w.getPVP() ? " (pvp)" : " (no pvp)")); + } + return enabled.toArray(new String[0]); + } + + public static String serverWorldNamesStr(Server sv) { + String allworlds[] = ServerInfo.serverWorldNames(sv), + worlds = ""; + for (int i = 0; i < allworlds.length; ++i) { + worlds += allworlds[i]; + if (i + 1 < allworlds.length) { + worlds += ", "; + } + } + return worlds; + } +} // end class ServerInfo + diff --git a/src/me/jascotty2/lib/bukkit/commands/Command.java b/src/me/jascotty2/lib/bukkit/commands/Command.java new file mode 100644 index 0000000..f52b49d --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/Command.java @@ -0,0 +1,62 @@ +// $Id$ +/* + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Command { + /** + * A list of commands that can be used for the command + */ + String[] commands(); + /** + * A list of aliases for the command (for nested commands) + */ + String[] aliases() default {}; + + /** + * Usage instruction. Example text for usage could be + * [-h] [name] [message]. + */ + String usage() default ""; + + /** + * A short description for the command. + */ + String desc(); + + /** + * The minimum number of arguments. This should be 0 or above. + */ + int min() default 0; + + /** + * The maximum number of arguments. Use -1 for an unlimited number + * of arguments. + */ + int max() default -1; + + /** + * Permission node to use this command + */ + String[] permissions() default ""; +} diff --git a/src/me/jascotty2/lib/bukkit/commands/CommandException.java b/src/me/jascotty2/lib/bukkit/commands/CommandException.java new file mode 100644 index 0000000..f10a1e5 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/CommandException.java @@ -0,0 +1,38 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +public class CommandException extends Exception { + private static final long serialVersionUID = 870638193072101739L; + + public CommandException() { + super(); + } + + public CommandException(String message) { + super(message); + } + + public CommandException(Throwable t) { + super(t); + } + +} diff --git a/src/me/jascotty2/lib/bukkit/commands/CommandManager.java b/src/me/jascotty2/lib/bukkit/commands/CommandManager.java new file mode 100644 index 0000000..d775104 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/CommandManager.java @@ -0,0 +1,378 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: Generic Commands Manager..
+ * much of the code was borrowed from sk89q's WorldEdit + * https://github.com/sk89q/worldedit + * (why reinvent the wheel? :) + * WorldEdit Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.lib.util.Str; +import org.bukkit.command.CommandSender; + +public abstract class CommandManager { + + protected final Map> commands = + new HashMap>(); + protected Map descs = new HashMap(); + + protected abstract Logger getLogger(); + + public void registerCommandClass(Class c) { + registerCommandClass(c, null); + } + + public void registerCommandClass(Class c, Method parent) { + if (c == null) { + return; + } + Map map; + if (commands.containsKey(parent)) { + map = commands.get(parent); + } else { + map = new HashMap(); + commands.put(parent, map); + } + + for (Method method : c.getMethods()) { + if (!method.isAnnotationPresent(Command.class) + || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + Command cmd = method.getAnnotation(Command.class); + + for (String alias : cmd.commands()) { + if (map.containsKey(alias)) { +// System.out.println("notice: duplicate key '" + alias +// + "' in " + (parent == null ? "root" : parent.getName())); + } else { + map.put(alias, method); +// System.out.println("registering " + (parent == null ? "root" : parent.getName()) +// + ":" + alias + " to " + method); +// if(alias.equalsIgnoreCase("region")){ +// System.out.println("mapping: " + Str.concatStr(map.keySet(), ", ")); + } + } + if (parent != null) { + // Cache the aliases + for (String alias : cmd.aliases()) { + if (!map.containsKey(alias)) { + map.put(alias, method); + } + } + } + + // Build a list of commands and their usage details, + // at least for root level commands + if (parent == null && cmd.commands().length > 0) { + if (cmd.usage().length() == 0) { + descs.put(cmd.commands()[0], cmd.desc()); + } else { + descs.put(cmd.commands()[0], cmd.usage() + " - " + cmd.desc()); + } + } + + // Look for nested commands -- if there are any, those have + // to be cached too so that they can be quickly looked + // up when processing commands + if (method.isAnnotationPresent(NestedCommand.class)) { + for (Class nestedCls : method.getAnnotation(NestedCommand.class).value()) { +// System.out.println("regisering " + nestedCls + " under " + method.getName()); + registerCommandClass(nestedCls, method); + } + } + } + } + + /** + * Checks to see whether there is a command named such at the root level. + * This will check aliases as well. + * + * @param command + * @return + */ + public boolean hasCommand(String command) { + return commands.get(null).containsKey(command.toLowerCase()); + } + + /** + * Get the usage string for a command. + * + * @param args + * @param level + * @param cmd + * @return + */ + protected String getUsage(String[] args, int level, Command cmd) { + StringBuilder command = new StringBuilder("/"); + + for (int i = 0; i <= level; ++i) { + command.append(args[i]).append(" "); + } + command.append(cmd.usage()); + + return command.toString(); + } + + /** + * Get the usage string for a nested command. + * + * @param player + * @param args + * @param level + * @param method + * @return + * @throws CommandException + */ + protected String getNestedUsage(CommandSender player, String[] args, + int level, Method method) throws CommandException { + StringBuilder command = new StringBuilder(); + + command.append("/"); + for (int i = 0; i <= level; ++i) { + command.append(args[i]).append(" "); + } + command.append(" "); + + Map map = commands.get(method); + if (map == null) { + return command.toString(); // shouldn't get here, really.. + } + boolean found = false; + + command.append("<"); + + Set allowedCommands = new HashSet(); + + for (Entry entry : map.entrySet()) { + Method childMethod = entry.getValue(); + found = true; + + if (checkHasPermission(player, childMethod)) { + Command childCmd = childMethod.getAnnotation(Command.class); + if (childCmd.aliases().length > 1) { + allowedCommands.add(childCmd.aliases()[0]); + } else if (childCmd.commands().length > 1) { + allowedCommands.add(childCmd.commands()[0]); + } + } + } + + if (allowedCommands.size() > 0) { + command.append(Str.concatStr(allowedCommands, "|")); + } else { + if (!found) { + command.append("?"); + } else { + throw new CommandPermissionsException(); + } + } + + command.append(">"); + + return command.toString(); + } + + /** + * Returns whether a player has access to a command. + * + * @param method + * @param player + * @return + */ + protected boolean checkHasPermission(CommandSender player, Method method) { + //CommandPermissions perms = method.getAnnotation(CommandPermissions.class); + Command perms = method.getAnnotation(Command.class); + if (perms == null + || perms.permissions() == null + || perms.permissions().length == 0) { + return true; + } + + for (String perm : perms.permissions()) { + if (hasPermission(player, perm)) { + return true; + } + } + + return false; + } + + /** + * Returns whether a player permission.. + * + * @param player + * @param perm + * @return + */ + public abstract boolean hasPermission(CommandSender player, String perm); + + /** + * Attempt to execute a command. + * + * @param player + * @param args + * @throws CommandException + */ + public void execute(CommandSender player, String... args) throws CommandException { + if (args.length == 0) { + throw new UnhandledCommandException(); + } /*else if(args.length >= 1) { + // String[] newArgs = new String[args.length - 1]; + // System.arraycopy(args, 1, newArgs, 0, newArgs.length); + // + // executeMethod(player, args[0], newArgs, null, 0); + }*/ else { +// executeMethod(player, args[0], new String[0], null, 0); + executeMethod(player, args[0], args, null, 0); + } + } + + /** + * Attempt to execute a command. This version takes a separate command + * name (for the root command) and then a list of following arguments. + * + * @param player command source + * @param cmd command to run + * @param args arguments + * @throws CommandException + */ + public void execute(CommandSender player, String cmd, String[] args) throws CommandException { + String[] allArgs = new String[args.length + 1]; + allArgs[0] = cmd; + System.arraycopy(args, 0, allArgs, 1, args.length); + + executeMethod(player, cmd, allArgs, null, 0); + //executeMethod(player, cmd, args, null, 0); + } + + /** + * Attempt to execute a command. + * + * @param player + * @param cmd Command Executed + * @param args Command arguments + * @param parent + * @throws CommandException + */ + public void executeMethod(CommandSender player, String cmd, String[] args, Method parent) throws CommandException { + String[] allArgs = new String[args.length + 1]; + allArgs[0] = cmd; + System.arraycopy(args, 0, allArgs, 1, args.length); + + executeMethod(player, cmd, allArgs, null, 0); + //executeMethod(player, cmd, args, parent, 0); + } + + /** + * Attempt to execute a command. + * + * @param player + * @param cmd Command Executed + * @param args Command arguments + * @param parent + * @param level + * @throws CommandException + */ + public void executeMethod(CommandSender player, String cmd, String[] args, Method parent, int level) throws CommandException { + //System.out.println(cmd + " " + Str.concatStr(args, " ") + " " + parent); + Map map = commands.get(parent); + Method method = map.get(cmd.toLowerCase()); + + if (method == null) { + if (parent == null) { // Root +// //System.out.println(Str.concatStr(commands.keySet(), "\n")); +// for (Method m : commands.keySet()) { +// System.out.println((m == null ? "null" : m.toString()) + ":"); +// for (String c : commands.get(m).keySet()) { +// System.out.println(c); +// } +// } +// System.out.println(Str.concatStr(map.values(), "\n")); +// System.out.println(Str.concatStr(map.keySet(), ", ")); +// System.out.println(); +// System.out.println(Str.concatStr(commands.keySet(), "\n")); + + throw new UnhandledCommandException(); + } else { + throw new MissingNestedCommandException("Unknown command: " + cmd, + getNestedUsage(player, args, 0, parent)); + } + } + + if (!checkHasPermission(player, method)) { + Command perms = method.getAnnotation(Command.class); + throw new CommandPermissionsException(perms.permissions()); + } + + if (method.isAnnotationPresent(NestedCommand.class)) { + if (args.length - level == 1) { + throw new MissingNestedCommandException("Sub-command required.", + getNestedUsage(player, args, level, method)); + } else { +// System.out.println("following nested command to " + method.getName() + " (" + (level+1) + ")"); +// String[] newArgs = new String[args.length - 1]; +// System.arraycopy(args, 1, newArgs, 0, newArgs.length); +// executeMethod(player, args[0], newArgs, method, level + 1); + executeMethod(player, args[level + 1], args, method, level + 1); + return; + } + } + + Command cmdInfo = method.getAnnotation(Command.class); + + if (args.length - (level + 1) < cmdInfo.min()) { + throw new CommandUsageException("Too few arguments.", + getUsage(args, level, cmdInfo)); + } + + if (cmdInfo.max() != -1 && args.length - (level + 1) > cmdInfo.max()) { + throw new CommandUsageException("Too many arguments.", + getUsage(args, level, cmdInfo)); + } + + try { + //method.invoke(instance, (Object[])args); + //method.invoke(null, (Object[]) args); + String[] newArgs = new String[args.length - 1 - level]; + System.arraycopy(args, level + 1, newArgs, 0, newArgs.length); + method.invoke(null, new Object[]{player, newArgs}); + } catch (IllegalArgumentException e) { + getLogger().log(Level.SEVERE, "Failed to execute command", e); + } catch (IllegalAccessException e) { + getLogger().log(Level.SEVERE, "Failed to execute command", e); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof CommandException) { + throw (CommandException) e.getCause(); + } + + throw new WrappedCommandException(e.getCause()); + } + } +} // end class CommandManager + diff --git a/src/me/jascotty2/lib/bukkit/commands/CommandPermissionsException.java b/src/me/jascotty2/lib/bukkit/commands/CommandPermissionsException.java new file mode 100644 index 0000000..e176c4c --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/CommandPermissionsException.java @@ -0,0 +1,35 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +/** + * Thrown when not enough permissions are satisfied. + * + * @author sk89q + */ +public class CommandPermissionsException extends CommandException { + private static final long serialVersionUID = -602374621030168291L; + String node = null; + public CommandPermissionsException(){} + public CommandPermissionsException(String node){this.node = node;} + public CommandPermissionsException(String[] node){this.node = node != null && node.length >= 1 ? node[0] : null;} + public String getNode(){return node;} +} diff --git a/src/me/jascotty2/lib/bukkit/commands/CommandUsageException.java b/src/me/jascotty2/lib/bukkit/commands/CommandUsageException.java new file mode 100644 index 0000000..2548890 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/CommandUsageException.java @@ -0,0 +1,36 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +public class CommandUsageException extends CommandException { + private static final long serialVersionUID = -6761418114414516542L; + + protected String usage; + + public CommandUsageException(String message, String usage) { + super(message); + this.usage = usage; + } + + public String getUsage() { + return usage; + } +} diff --git a/src/me/jascotty2/lib/bukkit/commands/MissingNestedCommandException.java b/src/me/jascotty2/lib/bukkit/commands/MissingNestedCommandException.java new file mode 100644 index 0000000..f08ec8f --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/MissingNestedCommandException.java @@ -0,0 +1,30 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +public class MissingNestedCommandException extends CommandUsageException { + private static final long serialVersionUID = -4382896182979285355L; + + public MissingNestedCommandException(String message, String usage) { + super(message, usage); + } + +} diff --git a/src/me/jascotty2/lib/bukkit/commands/NestedCommand.java b/src/me/jascotty2/lib/bukkit/commands/NestedCommand.java new file mode 100644 index 0000000..f9648d8 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/NestedCommand.java @@ -0,0 +1,42 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates a nested command. Mark methods with this annotation to tell + * {@link CommandsManager} that a method is merely a shell for child + * commands. Note that the body of a method marked with this annotation + * will never called. Additionally, not all fields of {@link Command} apply + * when it is used in conjunction with this annotation, although both + * are still required. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NestedCommand { + /** + * @return A list of classes with the child commands. + */ + Class[] value(); +} diff --git a/src/me/jascotty2/lib/bukkit/commands/UnhandledCommandException.java b/src/me/jascotty2/lib/bukkit/commands/UnhandledCommandException.java new file mode 100644 index 0000000..e690df9 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/UnhandledCommandException.java @@ -0,0 +1,26 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +public class UnhandledCommandException extends CommandException { + private static final long serialVersionUID = 3370887306593968091L; + +} diff --git a/src/me/jascotty2/lib/bukkit/commands/WrappedCommandException.java b/src/me/jascotty2/lib/bukkit/commands/WrappedCommandException.java new file mode 100644 index 0000000..f67dd31 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/commands/WrappedCommandException.java @@ -0,0 +1,29 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ +//package com.sk89q.minecraft.util.commands; + +package me.jascotty2.lib.bukkit.commands; + +public class WrappedCommandException extends CommandException { + private static final long serialVersionUID = -4075721444847778918L; + + public WrappedCommandException(Throwable t) { + super(t); + } +} diff --git a/src/me/jascotty2/lib/bukkit/config/Configuration.java b/src/me/jascotty2/lib/bukkit/config/Configuration.java new file mode 100644 index 0000000..62165b1 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/config/Configuration.java @@ -0,0 +1,229 @@ +package me.jascotty2.lib.bukkit.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.introspector.Property; +import org.yaml.snakeyaml.nodes.CollectionNode; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.SequenceNode; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.reader.UnicodeReader; +import org.yaml.snakeyaml.representer.Represent; +import org.yaml.snakeyaml.representer.Representer; + +/** + * YAML configuration loader. To use this class, construct it with path to + * a file and call its load() method. For specifying node paths in the + * various get*() methods, they support SK's path notation, allowing you to + * select child nodes by delimiting node names with periods. + * + *

+ * For example, given the following configuration file:

+ * + *
members:
+ *     - Hollie
+ *     - Jason
+ *     - Bobo
+ *     - Aya
+ *     - Tetsu
+ * worldguard:
+ *     fire:
+ *         spread: false
+ *         blocks: [cloth, rock, glass]
+ * sturmeh:
+ *     cool: false
+ *     eats:
+ *         babies: true
+ * + *

Calling code could access sturmeh's baby eating state by using + * getBoolean("sturmeh.eats.babies", false). For lists, there are + * methods such as getStringList that will return a type safe list. + * + *

This class is currently incomplete. It is not yet possible to get a node. + *

+ * + */ +public class Configuration extends ConfigurationNode { + private Yaml yaml; + private File file; + private String header = null; + + public Configuration(File file) { + super(new HashMap()); + + DumperOptions options = new DumperOptions(); + + options.setIndent(4); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + yaml = new Yaml(new SafeConstructor(), new EmptyNullRepresenter(), options); + + this.file = file; + } + + /** + * Loads the configuration file. All errors are thrown away. + */ + public void load() { + FileInputStream stream = null; + + try { + stream = new FileInputStream(file); + read(yaml.load(new UnicodeReader(stream))); + } catch (IOException e) { + root = new HashMap(); + } catch (ConfigurationException e) { + root = new HashMap(); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) {} + } + } + + /** + * Set the header for the file as a series of lines that are terminated + * by a new line sequence. + * + * @param headerLines header lines to prepend + */ + public void setHeader(String... headerLines) { + StringBuilder header = new StringBuilder(); + + for (String line : headerLines) { + if (header.length() > 0) { + header.append("\r\n"); + } + header.append(line); + } + + setHeader(header.toString()); + } + + /** + * Set the header for the file. A header can be provided to prepend the + * YAML data output on configuration save. The header is + * printed raw and so must be manually commented if used. A new line will + * be appended after the header, however, if a header is provided. + * + * @param header header to prepend + */ + public void setHeader(String header) { + this.header = header; + } + + /** + * Return the set header. + * + * @return The header comment. + */ + public String getHeader() { + return header; + } + + /** + * Saves the configuration to disk. All errors are clobbered. + * + * @return true if it was successful + */ + public boolean save() { + FileOutputStream stream = null; + + File parent = file.getParentFile(); + + if (parent != null) { + parent.mkdirs(); + } + + try { + stream = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8"); + if (header != null) { + writer.append(header); + writer.append("\r\n"); + } + yaml.dump(root, writer); + return true; + } catch (IOException e) {} finally { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) {} + } + + return false; + } + + @SuppressWarnings("unchecked") + private void read(Object input) throws ConfigurationException { + try { + if (null == input) { + root = new HashMap(); + } else { + root = (Map) input; + } + } catch (ClassCastException e) { + throw new ConfigurationException("Root document must be an key-value structure"); + } + } + + /** + * This method returns an empty ConfigurationNode for using as a + * default in methods that select a node from a node list. + * @return The empty node. + */ + public static ConfigurationNode getEmptyNode() { + return new ConfigurationNode(new HashMap()); + } +} + +class EmptyNullRepresenter extends Representer { + + public EmptyNullRepresenter() { + super(); + this.nullRepresenter = new EmptyRepresentNull(); + } + + protected class EmptyRepresentNull implements Represent { + public Node representData(Object data) { + return representScalar(Tag.NULL, ""); // Changed "null" to "" so as to avoid writing nulls + } + } + + // Code borrowed from snakeyaml (http://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue60/SkipBeanTest.java) + @Override + protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) { + NodeTuple tuple = super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); + Node valueNode = tuple.getValueNode(); + if (valueNode instanceof CollectionNode) { + // Removed null check + if (Tag.SEQ.equals(valueNode.getTag())) { + SequenceNode seq = (SequenceNode) valueNode; + if (seq.getValue().isEmpty()) { + return null; // skip empty lists + } + } + if (Tag.MAP.equals(valueNode.getTag())) { + MappingNode seq = (MappingNode) valueNode; + if (seq.getValue().isEmpty()) { + return null; // skip empty maps + } + } + } + return tuple; + } + // End of borrowed code +} diff --git a/src/me/jascotty2/lib/bukkit/config/ConfigurationException.java b/src/me/jascotty2/lib/bukkit/config/ConfigurationException.java new file mode 100644 index 0000000..478f7a1 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/config/ConfigurationException.java @@ -0,0 +1,18 @@ +package me.jascotty2.lib.bukkit.config; + +/** + * Configuration exception. + * + * @author sk89q + */ +public class ConfigurationException extends Exception { + private static final long serialVersionUID = -2442886939908724203L; + + public ConfigurationException() { + super(); + } + + public ConfigurationException(String msg) { + super(msg); + } +} diff --git a/src/me/jascotty2/lib/bukkit/config/ConfigurationNode.java b/src/me/jascotty2/lib/bukkit/config/ConfigurationNode.java new file mode 100644 index 0000000..f1d8cfa --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/config/ConfigurationNode.java @@ -0,0 +1,587 @@ +package me.jascotty2.lib.bukkit.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Represents a configuration node. + */ +public class ConfigurationNode { + protected Map root; + + protected ConfigurationNode(Map root) { + this.root = root; + } + + /** + * Gets all of the cofiguration values within the Node as + * a key value pair, with the key being the full path and the + * value being the Object that is at the path. + * + * @return A map of key value pairs with the path as the key and the object as the value + */ + public Map getAll() { + return recursiveBuilder(root); + } + + /** + * A helper method for the getAll method that deals with the recursion + * involved in traversing the tree + * + * @param node The map for that node of the tree + * @return The fully pathed map for that point in the tree, with the path as the key + */ + @SuppressWarnings("unchecked") + protected Map recursiveBuilder(Map node) { + Map map = new TreeMap(); + + Set keys = node.keySet(); + for( String k : keys ) { + Object tmp = node.get(k); + if( tmp instanceof Map ) { + Map rec = recursiveBuilder((Map ) tmp); + + Set subkeys = rec.keySet(); + for( String sk : subkeys ) { + map.put(k + "." + sk, rec.get(sk)); + } + } + else { + map.put(k, tmp); + } + } + + return map; + } + + /** + * Gets a property at a location. This will either return an Object + * or null, with null meaning that no configuration value exists at + * that location. This could potentially return a default value (not yet + * implemented) as defined by a plugin, if this is a plugin-tied + * configuration. + * + * @param path path to node (dot notation) + * @return object or null + */ + @SuppressWarnings("unchecked") + public Object getProperty(String path) { + if (!path.contains(".")) { + Object val = root.get(path); + + if (val == null) { + return null; + } + return val; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + if (o == null) { + return null; + } + + if (i == parts.length - 1) { + return o; + } + + try { + node = (Map) o; + } catch (ClassCastException e) { + return null; + } + } + + return null; + } + + /** + * Set the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path The property path + * @param value The new value + */ + @SuppressWarnings("unchecked") + public void setProperty(String path, Object value) { + if (!path.contains(".")) { + root.put(path, value); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.put(parts[i], value); + return; + } + + if (o == null || !(o instanceof Map)) { + // This will override existing configuration data! + o = new HashMap(); + node.put(parts[i], o); + } + + node = (Map) o; + } + } + + /** + * Gets a string at a location. This will either return an String + * or null, with null meaning that no configuration value exists at + * that location. If the object at the particular location is not actually + * a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @return string or null + */ + public String getString(String path) { + Object o = getProperty(path); + + if (o == null) { + return null; + } + return o.toString(); + } + + /** + * Gets a string at a location. This will either return an String + * or the default value. If the object at the particular location is not + * actually a string, it will be converted to its string representation. + * + * @param path path to node (dot notation) + * @param def default value + * @return string or default + */ + public String getString(String path, String def) { + String o = getString(path); + + if (o == null) { + setProperty(path, def); + return def; + } + return o; + } + + /** + * Gets an integer at a location. This will either return an integer + * or the default value. If the object at the particular location is not + * actually a integer, the default value will be returned. However, other + * number types will be casted to an integer. + * + * @param path path to node (dot notation) + * @param def default value + * @return int or default + */ + public int getInt(String path, int def) { + Integer o = castInt(getProperty(path)); + + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a double at a location. This will either return an double + * or the default value. If the object at the particular location is not + * actually a double, the default value will be returned. However, other + * number types will be casted to an double. + * + * @param path path to node (dot notation) + * @param def default value + * @return double or default + */ + public double getDouble(String path, double def) { + Double o = castDouble(getProperty(path)); + + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Gets a boolean at a location. This will either return an boolean + * or the default value. If the object at the particular location is not + * actually a boolean, the default value will be returned. + * + * @param path path to node (dot notation) + * @param def default value + * @return boolean or default + */ + public boolean getBoolean(String path, boolean def) { + Boolean o = castBoolean(getProperty(path)); + + if (o == null) { + setProperty(path, def); + return def; + } else { + return o; + } + } + + /** + * Get a list of keys at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return list of keys + */ + @SuppressWarnings("unchecked") + public List getKeys(String path) { + if (path == null) { + return new ArrayList(root.keySet()); + } + Object o = getProperty(path); + + if (o == null) { + return null; + } else if (o instanceof Map) { + return new ArrayList(((Map) o).keySet()); + } else { + return null; + } + } + + /** + * Returns a list of all keys at the root path + * + * @return List of keys + */ + public List getKeys() { + return new ArrayList(root.keySet()); + } + + /** + * Gets a list of objects at a location. If the list is not defined, + * null will be returned. The node must be an actual list. + * + * @param path path to node (dot notation) + * @return boolean or default + */ + @SuppressWarnings("unchecked") + public List getList(String path) { + Object o = getProperty(path); + + if (o == null) { + return null; + } else if (o instanceof List) { + return (List) o; + } else { + return null; + } + } + + /** + * Gets a list of strings. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. If an item in the list + * is not a string, it will be converted to a string. The node must be + * an actual list and not just a string. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of strings + */ + public List getStringList(String path, List def) { + List raw = getList(path); + + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + + for (Object o : raw) { + if (o == null) { + continue; + } + + list.add(o.toString()); + } + + return list; + } + + /** + * Gets a list of integers. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and not just an integer. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getIntList(String path, List def) { + List raw = getList(path); + + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + + for (Object o : raw) { + Integer i = castInt(o); + + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of doubles. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a double. + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getDoubleList(String path, List def) { + List raw = getList(path); + + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + + for (Object o : raw) { + Double i = castDouble(o); + + if (i != null) { + list.add(i); + } + } + + return list; + } + + /** + * Gets a list of booleans. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual list and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + public List getBooleanList(String path, List def) { + List raw = getList(path); + + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + + for (Object o : raw) { + Boolean tetsu = castBoolean(o); + + if (tetsu != null) { + list.add(tetsu); + } + } + + return list; + } + + /** + * Gets a list of nodes. Non-valid entries will not be in the list. + * There will be no null slots. If the list is not defined, the + * default will be returned. 'null' can be passed for the default + * and an empty list will be returned instead. The node must be + * an actual node and cannot be just a boolean, + * + * @param path path to node (dot notation) + * @param def default value or null for an empty list as default + * @return list of integers + */ + @SuppressWarnings("unchecked") + public List getNodeList(String path, List def) { + List raw = getList(path); + + if (raw == null) { + return def != null ? def : new ArrayList(); + } + + List list = new ArrayList(); + + for (Object o : raw) { + if (o instanceof Map) { + list.add(new ConfigurationNode((Map) o)); + } + } + + return list; + } + + /** + * Get a configuration node at a path. If the node doesn't exist or the + * path does not lead to a node, null will be returned. A node has + * key/value mappings. + * + * @param path The property path + * @return node or null + */ + @SuppressWarnings("unchecked") + public ConfigurationNode getNode(String path) { + Object raw = getProperty(path); + + if (raw instanceof Map) { + return new ConfigurationNode((Map) raw); + } + + return null; + } + + /** + * Get a list of nodes at a location. If the map at the particular location + * does not exist or it is not a map, null will be returned. + * + * @param path path to node (dot notation) + * @return map of nodes + */ + @SuppressWarnings("unchecked") + public Map getNodes(String path) { + Object o = getProperty(path); + + if (o == null) { + return null; + } else if (o instanceof Map) { + Map nodes = new HashMap(); + + for (Map.Entry entry : ((Map) o).entrySet()) { + if (entry.getValue() instanceof Map) { + nodes.put(entry.getKey(), new ConfigurationNode((Map) entry.getValue())); + } + } + + return nodes; + } else { + return null; + } + } + + /** + * Casts a value to an integer. May return null. + * + * @param o + * @return + */ + private static Integer castInt(Object o) { + if (o == null) { + return null; + } else if (o instanceof Byte) { + return (int) (Byte) o; + } else if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof Double) { + return (int) (double) (Double) o; + } else if (o instanceof Float) { + return (int) (float) (Float) o; + } else if (o instanceof Long) { + return (int) (long) (Long) o; + } else { + return null; + } + } + + /** + * Casts a value to a double. May return null. + * + * @param o + * @return + */ + private static Double castDouble(Object o) { + if (o == null) { + return null; + } else if (o instanceof Float) { + return (double) (Float) o; + } else if (o instanceof Double) { + return (Double) o; + } else if (o instanceof Byte) { + return (double) (Byte) o; + } else if (o instanceof Integer) { + return (double) (Integer) o; + } else if (o instanceof Long) { + return (double) (Long) o; + } else { + return null; + } + } + + /** + * Casts a value to a boolean. May return null. + * + * @param o + * @return + */ + private static Boolean castBoolean(Object o) { + if (o == null) { + return null; + } else if (o instanceof Boolean) { + return (Boolean) o; + } else { + return null; + } + } + + /** + * Remove the property at a location. This will override existing + * configuration data to have it conform to key/value mappings. + * + * @param path The property path + */ + @SuppressWarnings("unchecked") + public void removeProperty(String path) { + if (!path.contains(".")) { + root.remove(path); + return; + } + + String[] parts = path.split("\\."); + Map node = root; + + for (int i = 0; i < parts.length; i++) { + Object o = node.get(parts[i]); + + // Found our target! + if (i == parts.length - 1) { + node.remove(parts[i]); + return; + } + + node = (Map) o; + } + } +} diff --git a/src/me/jascotty2/lib/bukkit/inventory/ChestManip.java b/src/me/jascotty2/lib/bukkit/inventory/ChestManip.java new file mode 100644 index 0000000..a6b0c9c --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/inventory/ChestManip.java @@ -0,0 +1,215 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for manipulating the items in a chest + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.inventory; + +import java.util.ArrayList; +import java.util.List; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Chest; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class ChestManip { + + public synchronized static boolean containsItem(Chest chest, Material check) { + Chest otherChest = otherChest(chest.getBlock()); + + return ItemStackManip.contains(chest.getInventory().getContents(), check) + || (otherChest != null && ItemStackManip.contains(otherChest.getInventory().getContents(), check)); + + } + + public synchronized static boolean containsItem(Chest chest, Material check, short damage) { + Chest otherChest = otherChest(chest.getBlock()); + + return ItemStackManip.contains(chest.getInventory().getContents(), check, damage) + || (otherChest != null && ItemStackManip.contains(otherChest.getInventory().getContents(), check)); + + } + + public synchronized static void removeItem(Chest chest, Material check) { + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + chest.getInventory().setContents(ItemStackManip.remove(chest.getInventory().getContents(), check)); + } else { + if (ItemStackManip.contains(chest.getInventory().getContents(), check)) { + chest.getInventory().setContents(ItemStackManip.remove(chest.getInventory().getContents(), check)); + } else { + otherChest.getInventory().setContents(ItemStackManip.remove(otherChest.getInventory().getContents(), check)); + } + } + } + + public synchronized static void removeItem(Chest chest, Material check, short damage) { + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + chest.getInventory().setContents(ItemStackManip.remove(chest.getInventory().getContents(), check)); + } else { + if (ItemStackManip.contains(chest.getInventory().getContents(), check, damage)) { + chest.getInventory().setContents(ItemStackManip.remove(chest.getInventory().getContents(), check, damage)); + } else { + otherChest.getInventory().setContents(ItemStackManip.remove(otherChest.getInventory().getContents(), check, damage)); + } + } + } + + public synchronized static ItemStack[] getContents(Chest chest) { + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + return chest.getInventory().getContents(); + } else { + ItemStack iss[] = new ItemStack[27 * 2]; + // return with the top portion first + if (topChest(chest) == chest) { + System.arraycopy(chest.getInventory().getContents(), 0, iss, 0, 27); + System.arraycopy(otherChest.getInventory().getContents(), 0, iss, 27, 27); + } else { + System.arraycopy(otherChest.getInventory().getContents(), 0, iss, 0, 27); + System.arraycopy(chest.getInventory().getContents(), 0, iss, 27, 27); + } + return iss; + } + } + + public synchronized static List getItems(Chest c) { + ItemStack[] chest = getContents(c); + ArrayList chestItems = new ArrayList(); + for (ItemStack i : chest) { + JItem it = JItemDB.GetItem(i); + if (!chestItems.contains(it)) { + chestItems.add(it); + } + } + return chestItems; + } + + public static void setContents(Chest chest, ItemStack iss[]) { + setContents(chest, iss, true); + } + + public synchronized static void setContents(Chest chest, ItemStack iss[], boolean useOrder) { + if(chest == null) return; + if(iss.length == 27) { + chest.getInventory().setContents(iss); + } else if (iss.length == 27 * 2) { + // new bukkit changed this method completely.. :/ + //ItemStack iss1[] = new ItemStack[27]; + //ItemStack iss2[] = new ItemStack[27]; + //System.arraycopy(iss, 0, iss1, 0, iss1.length); + //System.arraycopy(iss, 27, iss2, 0, iss2.length); + + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + //chest.getInventory().setContents(iss1); + chest.getInventory().setContents(iss); + } else { + if (!useOrder || topChest(chest) == chest) { + //chest.getInventory().setContents(iss1); + //otherChest.getInventory().setContents(iss2); + chest.getInventory().setContents(iss); + } else { + //otherChest.getInventory().setContents(iss1); + //chest.getInventory().setContents(iss2); + otherChest.getInventory().setContents(iss); + } + } + } + } + + + public synchronized static void addContents(Chest chest, ItemStack is) { + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + chest.getInventory().addItem(is); + } else { + if (topChest(chest) == chest) { + if (!ItemStackManip.is_full(chest.getInventory().getContents(), is)) { + chest.getInventory().addItem(is); + } else { + otherChest.getInventory().addItem(is); + } + } else { // if (!is_full(chest.getInventory().getContents(), is)) { + if (!ItemStackManip.is_full(otherChest.getInventory().getContents(), is)) { + otherChest.getInventory().addItem(is); + } else { + chest.getInventory().addItem(is); + } + } + } + } + + /** + * add contents of the stack to the chest, allowing more stacking + * @param chest + * @param is + */ + public synchronized static void addContentsStack(Chest chest, ItemStack is) { + Chest otherChest = otherChest(chest.getBlock()); + if (otherChest == null) { + chest.getInventory().addItem(is); + } else { + if (otherChest.getX() < chest.getX() + || otherChest.getZ() < chest.getZ()) { + if (!ItemStackManip.is_full(otherChest.getInventory().getContents(), is, true)) { + otherChest.getInventory().setContents(ItemStackManip.add(otherChest.getInventory().getContents(), is)); + } else { + chest.getInventory().setContents(ItemStackManip.add(chest.getInventory().getContents(), is)); + } + } else { // if (!is_full(chest.getInventory().getContents(), is)) { + if (!ItemStackManip.is_full(chest.getInventory().getContents(), is, true)) { + chest.getInventory().setContents(ItemStackManip.add(chest.getInventory().getContents(), is)); + } else { + otherChest.getInventory().setContents(ItemStackManip.add(otherChest.getInventory().getContents(), is)); + } + } + } + } + + public synchronized static Chest otherChest(Block bl) { + if (bl == null) { + return null; + } else if (bl.getRelative(BlockFace.NORTH).getType() == Material.CHEST) { + return (Chest) bl.getRelative(BlockFace.NORTH).getState(); + } else if (bl.getRelative(BlockFace.WEST).getType() == Material.CHEST) { + return (Chest) bl.getRelative(BlockFace.WEST).getState(); + } else if (bl.getRelative(BlockFace.SOUTH).getType() == Material.CHEST) { + return (Chest) bl.getRelative(BlockFace.SOUTH).getState(); + } else if (bl.getRelative(BlockFace.EAST).getType() == Material.CHEST) { + return (Chest) bl.getRelative(BlockFace.EAST).getState(); + } + return null; + } + + public static Chest topChest(Chest c) { + if(c == null) return null; + Chest otherChest = otherChest(c.getBlock()); + if (otherChest != null && (otherChest.getX() < c.getX() + || otherChest.getZ() < c.getZ())) { + return otherChest; + } else { + return c; + } + } +} // end class ChestManip + diff --git a/src/me/jascotty2/lib/bukkit/inventory/ItemStackManip.java b/src/me/jascotty2/lib/bukkit/inventory/ItemStackManip.java new file mode 100644 index 0000000..2361ae2 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/inventory/ItemStackManip.java @@ -0,0 +1,619 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( static methods for manipulating itemstacks ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.inventory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.JItems; +import me.jascotty2.lib.bukkit.item.Kit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +public class ItemStackManip { + + /** + * items that should not stack + */ + // mushroom soup, buckets + public final static List noStack = Arrays.asList(282, 325, 326, 327, 335); + + /** + * check if this stack cannot hold one more of this item + * @param items + * @param check + * @return + */ + public static boolean is_full(ItemStack[] items, ItemStack check) { + return is_full(items, check, false); + } + + /** + * check if this stack cannot hold one more of this item + * @param items + * @param check + * @param extraStack if to assume is allowed to stack some unstackables + * @return + */ + public static boolean is_full(ItemStack[] items, ItemStack check, boolean extraStack) { + int amt = check.getAmount(); + for (ItemStack item : items) { + int mx = !extraStack || noStack.contains(item == null ? 0 : item.getTypeId()) + ? JItems.getMaxStack(item) : 64; + if (item == null || item.getAmount() == 0 + || (item.getTypeId() == check.getTypeId() + && (item.getAmount() + amt <= mx))) { + return false; + } else if (item.getTypeId() == check.getTypeId()) { + amt -= mx - item.getAmount(); + } + } + return true; + } + + /** + * count how many of this item is in the ItemStack + * @param items + * @param check + * @return + */ + public static int count(ItemStack[] items, JItem check) { + if (check == null) { + return emptySlots(items); + } + int amt = 0; + for (ItemStack item : items) { + if (item != null && check.equals(item)) { + amt += item.getAmount(); + } + } + return amt; + } + + public static int count(ItemStack[] items, JItem check, int start, int end) { + if (check == null) { + return emptySlots(items); + } + int amt = 0; + for (int i = start >= 0 ? start : 0; i < items.length && i <= end; ++i) { + ItemStack item = items[i]; + if (item != null && check.equals(item)) { + amt += item.getAmount(); + } + } + return amt; + } + /** + * count how many times this material occurs in the ItemStack + * @param items + * @param check + * @return + */ + public static int count(ItemStack[] items, Material check) { + if (check == null) { + return emptySlots(items); + } + int amt = 0; + for (ItemStack item : items) { + if (item != null && item.getType() == check) { + amt += item.getAmount(); + } + } + return amt; + } + + /** + * count how many slots in this ItemStack array are empty + * @param items + * @return + */ + public static int emptySlots(ItemStack[] items) { + int amt = 0; + if (items != null) { + for (ItemStack item : items) { + if (item == null || item.getAmount() <= 0) { + ++amt; + } + } + } + return amt; + } + + public static boolean contains(ItemStack[] items, Material check) { + if (items != null) { + if (check == null) { + for (ItemStack item : items) { + if (item == null) { + return true; + } + } + } else { + for (ItemStack item : items) { + if (item != null && item.getType() == check) { + return true; + } + } + } + } + return false; + } + + public static boolean contains(ItemStack[] items, Material check, short damage) { + if (items != null) { + if (check == null) { + for (ItemStack item : items) { + if (item == null) { + return true; + } + } + } else { + for (ItemStack item : items) { + if (item != null && item.getType() == check + && item.getDurability() == damage) { + return true; + } + } + } + } + return false; + } + + public static boolean canHold(ItemStack[] items, JItem check, int amt, boolean extraStack){ + return amountCanHold(items, check, extraStack) >= amt; + } + + public static int amountCanHold(ItemStack[] items, JItem check, boolean extraStack) { + int amt = 0; + if (items == null) { + return 0; + } else if (check == null) { + return emptySlots(items); + } else if (check.isEntity()) { + return -1; + } else if (check.isKit()) { + Kit kit = check instanceof Kit ? (Kit) check : JItemDB.getKit(check); + Kit.KitItem kititems[] = kit.getKitItems(); + ItemStack invCopy[] = copy(items); + // loop through & "add" one at a time + while (true) { + int numtoadd = 0; + for (int itn = 0; itn < kititems.length; ++itn) { + numtoadd = kititems[itn].itemAmount; + int maxStack = !extraStack || noStack.contains(kititems[itn].ID()) ? kititems[itn].MaxStackSize() : 64; + for (int i = 0; i < invCopy.length && numtoadd > 0; ++i) { + if (invCopy[i] == null || invCopy[i].getAmount() == 0) { + invCopy[i] = kititems[itn].toItemStack(); + invCopy[i].setAmount(numtoadd); + numtoadd -= numtoadd; + } else if (invCopy[i].getAmount() < maxStack && kititems[itn].iequals(invCopy[i])) { + int d = maxStack < numtoadd ? maxStack : numtoadd; + invCopy[i].setAmount(invCopy[i].getAmount() + d); + numtoadd -= d; + } + } + if (numtoadd > 0) { + // has scanned through full stack & cannot add more + return amt; + } + } + // 1 was added to the copy + ++amt; + } + } else { + for (ItemStack item : items) { + int mx = !extraStack || (item != null && noStack.contains(item.getTypeId())) ? check.MaxStackSize() : 64; + if (item == null || item.getAmount() == 0 + || (check.equals(item) && item.getAmount() <= mx)) { + amt += mx - (item == null ? 0 : item.getAmount()); + } + } + } + return amt; + } + + public static ItemStack[] remove(ItemStack[] items, ItemStack check) { + return remove(items, check, 0); + } + + public static ItemStack[] remove(ItemStack[] items, ItemStack check, int start) { + if (items != null) { + int total = check.getAmount(); + for (int i = start; i < items.length && total > 0; ++i) { + if (items[i] != null) { + if (items[i].getType() == check.getType() + && (items[i].getData() == null + || items[i].getData().getData() == check.getData().getData())) { + int a = items[i].getAmount(); + if (total < a) { + items[i].setAmount(a - total); + total = 0; + } else { + items[i] = null; + total -= a; + } + return items; + } + } + } + } + return items; + } + + public static ItemStack[] remove(ItemStack[] items, Material check) { + for (int i = 0; i < items.length; ++i) { + if (items[i] != null) { + if (items[i].getType() == check) { + if (items[i].getAmount() > 1) { + items[i].setAmount(items[i].getAmount() - 1); + } else { + items[i] = null; + } + return items; + } + } + } + return items; + } + + public static ItemStack[] remove(ItemStack[] items, Material check, short damage) { + for (int i = 0; i < items.length; ++i) { + if (items[i] != null) { + if (items[i].getType() == check && items[i].getDurability() == damage) { + if (items[i].getAmount() > 1) { + items[i].setAmount(items[i].getAmount() - 1); + } else { + items[i] = null; + } + return items; + } + } + } + return items; + } + + public static ItemStack[] remove(ItemStack[] items, JItem search, int amt) { + return remove(items, search, amt, 0); + } + + public static ItemStack[] remove(ItemStack[] items, JItem search, int total, int start) { + if (items == null) { + return null; + } else if (search == null) { + return items; + } else if (!search.isKit() && !search.isEntity()) { + for (int i = start; i < items.length && total > 0; ++i) { + if (items[i] != null && search.equals(items[i])) { + int a = items[i].getAmount(); + if (total < a) { + items[i].setAmount(a - total); + total = 0; + } else { + items[i] = null; + total -= a; + } + } + } + } + return items; + } + + public static ItemStack[] remove(ItemStack[] items, ItemStack[] search) { + return remove(items, search, 0); + } + + public static ItemStack[] remove(ItemStack[] items, ItemStack[] search, int start) { + for (ItemStack i : search) { + remove(items, i, start); + } + return items; + } + + public static ItemStack[] remove(ItemStack[] items, List search) { + return remove(items, search, 0); + } + + public static ItemStack[] remove(ItemStack[] items, List search, int start) { + for (ItemStack i : search) { + remove(items, i, start); + } + return items; + } + + public static ItemStack[] add(ItemStack[] items, JItem toAdd, int amt) { + return add(items, toAdd, amt, false); + } + + public static ItemStack[] add(ItemStack[] items, JItem toAdd, int amt, boolean extraStack) { + if (items == null) { + return null; + } else if (toAdd == null) { + return items; + } else if (toAdd.IsValidItem()) { + int mx = !extraStack || noStack.contains(toAdd.ID()) ? toAdd.MaxStackSize() : 64; + while(amt > 0){ + add(items, toAdd.toItemStack(amt > mx ? mx : amt), extraStack); + amt -= mx; + } + return items; + } else if (toAdd.isKit()) { + Kit kit = toAdd instanceof Kit ? (Kit) toAdd : JItemDB.getKit(toAdd); + Kit.KitItem kititems[] = kit.getKitItems(); + + // add one of each until fails + for (int num = 0; num < amt; ++num) { + int numtoadd = 0; + for (int itn = 0; itn < kit.numItems(); ++itn) { + numtoadd = kititems[itn].itemAmount; + int maxStack = !extraStack || noStack.contains(kititems[itn].ID()) ? kititems[itn].MaxStackSize() : 64; + for (int i = 0; i < items.length && numtoadd > 0; ++i) { + if (items[i] == null || items[i].getAmount() == 0) { + items[i] = kititems[itn].toItemStack(); + items[i].setAmount(numtoadd); + numtoadd -= numtoadd; + } else if (items[i].getAmount() < maxStack && kititems[itn].iequals(items[i])) { + int d = maxStack < numtoadd ? maxStack : numtoadd; + items[i].setAmount(items[i].getAmount() + d); + numtoadd -= d; + } + } + if (numtoadd > 0) { + break; + } + } + if (numtoadd > 0) { + //early exit while adding + break; + } + } + } + return items; + } + + /** + * add an ItemStack to another + * @param items + * @param toAdd + * @return + */ + public static ItemStack[] add(ItemStack[] items, ItemStack toAdd) { + return add(items, toAdd, false); + } + + public static ItemStack[] add(ItemStack[] items, List toAdd, boolean extraStack) { + for(ItemStack i : toAdd) { + items = add(items, i, extraStack); + } + return items; + } + + /** + * add an ItemStack to an array + * @param items + * @param toAdd + * @param extraStack whether to allow some nonstackable items to stack + * @return + */ + public static ItemStack[] add(ItemStack[] items, ItemStack toAdd, boolean extraStack) { + int amt = toAdd.getAmount(); + int mx = !extraStack || noStack.contains(toAdd.getTypeId()) + ? JItems.getMaxStack(toAdd) : 64; + boolean firstRun = true; + for (int i = 0; i < items.length; ++i) { + if (!firstRun && (items[i] == null || items[i].getAmount() == 0)) { + items[i] = toAdd; + items[i].setAmount(amt); + return items; + } else if (items[i] != null + && items[i].getTypeId() == toAdd.getTypeId() + && (!JItems.hasData(toAdd.getTypeId()) || items[i].getData().getData() == toAdd.getData().getData()) + && items[i].getAmount() < mx) { + // on first run, look for other stacks in array that could be incremented instead + if (items[i].getAmount() + amt <= mx) { + items[i].setAmount(items[i].getAmount() + amt); + return items; + } else { + amt -= mx - items[i].getAmount(); + items[i].setAmount(mx); + } + } else if (firstRun && i + 1 >= items.length) { + firstRun = false; + i = -1; // negative, because gets incremented again + } + } + return items; + } + + /** + * creates a summary of the itemstack array,
+ * so that each item is only listed once + * @param items + * @return + */ + public static List itemStackSummary(ItemStack[] items) { + return itemStackSummary(items, 0); + } + + public static List itemStackSummary(ItemStack[] items, int start) { + ArrayList summ = new ArrayList(); + if (items != null) { + for (int i = start; i < items.length; ++i) { + if (items[i] != null) { + int iti = indexOf(summ, items[i]); + if (iti < 0) { + summ.add(items[i].clone()); + } else { + summ.get(iti).setAmount(summ.get(iti).getAmount() + items[i].getAmount()); + } + } + } + } + return summ; + } + + public static List itemStackSummary(ItemStack[] items, JItem[] search, int start) { + ArrayList summ = new ArrayList(); + if (items != null) { + for (int i = start; i < items.length; ++i) { + if (items[i] != null && (search == null || JItem.contains(search, items[i]))) { + int iti = indexOf(summ, items[i]); + if (iti < 0) { + summ.add(items[i].clone()); + } else { + summ.get(iti).setAmount(summ.get(iti).getAmount() + items[i].getAmount()); + } + } + } + } + return summ; + } + + /** + * checks for differences between two itemstack arrays
+ * result amounts are second - first
+ * if there are less of an item in the second, the result will have a negative amount + * @param stack1 first to compare against + * @param stack2 second to check for differences + * @return list of results + */ + public static List itemStackDifferences(ItemStack[] stack1, ItemStack[] stack2) { + ArrayList changedItems = new ArrayList(); + if (stack1 == null) { + changedItems.addAll(Arrays.asList(copy(stack2))); + return changedItems; + } else if (stack2 == null) { + changedItems.addAll(Arrays.asList(copy(stack1))); + for(ItemStack i : changedItems) { + i.setAmount(-i.getAmount()); + } + return changedItems; + } + // first, compile list of items before shopping + List oldInventory = itemStackSummary(stack1); + // and after + List newInventory = itemStackSummary(stack2); + // list of differences + + // find those items that have changed / removed to the second + for (ItemStack i : oldInventory) { + int iti = indexOf(newInventory, i); + if (iti >= 0) { + // in second: check for changes + if (i.getAmount() != newInventory.get(iti).getAmount()) { + i.setAmount(newInventory.get(iti).getAmount() - i.getAmount()); + changedItems.add(i); + } + } else { + // not in the second + i.setAmount(-i.getAmount()); + changedItems.add(i); + } + } + // check for items added to the second + for (ItemStack i : newInventory) { + if (indexOf(oldInventory, i) == -1) { + // not in the first + changedItems.add(i); + } + } + return changedItems; + } + + /** + * ignoring amount, find the index of an itemstack in a list + * @param source list to search + * @param search search criteria + * @return index, or -1 if not found + */ + public static int indexOf(List source, ItemStack search) { + int ind = 0; + if (search == null) { + for (ItemStack i : source) { + if (i == null) { + return ind; + } + ++ind; + } + } else { + for (ItemStack i : source) { + if (i != null && i.getType() == search.getType() + && i.getDurability() == search.getDurability()){//&& (i.getData() == null || (i.getData().getData() == search.getData().getData()))) { + return ind; + } + ++ind; + } + } + return -1; + } + + /** + * ignoring amount, find the index of an itemstack in a list + * @param source list to search + * @param search search criteria + * @return index, or -1 if not found + */ + public static int indexOf(List source, JItem search) { + int ind = 0; + if (search == null) { + for (ItemStack i : source) { + if (i == null) { + return ind; + } + ++ind; + } + } else { + for (ItemStack i : source) { + if (search.equals(i)) { + return ind; + } + ++ind; + } + } + return -1; + } + /** + * makes a copy of a minecraft ItemStack as a bukkit ItemStack + * @param minecraftItemStack + * @return + */ + public static ItemStack[] copy(net.minecraft.server.v1_7_R1.ItemStack[] minecraftItemStack) { + ItemStack[] invCpy = new ItemStack[minecraftItemStack.length]; + for (int i = 0; i < minecraftItemStack.length; ++i) { + invCpy[i] = minecraftItemStack[i] == null ? null + : new ItemStack(minecraftItemStack[i].getItem().c(), + minecraftItemStack[i].count, + (short) minecraftItemStack[i].getData()); + } + return invCpy; + } + + /** + * makes a copy of a bukkit ItemStack + * @param items + * @return + */ + public static ItemStack[] copy(ItemStack[] items) { + ItemStack[] invCpy = new ItemStack[items.length]; + for (int i = 0; i < items.length; ++i) { + invCpy[i] = items[i] == null ? null : items[i].clone(); + } + return invCpy; + } +} // end class ItemStackManip + diff --git a/src/me/jascotty2/lib/bukkit/inventory/JInventory.java b/src/me/jascotty2/lib/bukkit/inventory/JInventory.java new file mode 100644 index 0000000..535b4fd --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/inventory/JInventory.java @@ -0,0 +1,293 @@ +/** + * Copyright (C) 2012 Jacob Scott + * Description: ( static methods for manipulating itemstacks ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.inventory; + +import me.jascotty2.lib.bukkit.item.JItems; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +public class JInventory { + + private final ItemStack[] inv; + /** + * if when adding or condensing can stack normally unstackable items + */ + protected boolean allowExtraStack = false; + + public JInventory() { + inv = new ItemStack[36]; + } + + public JInventory(PlayerInventory toCopy) { + if (toCopy == null) { + throw new NullPointerException("PlayerInventory cannot be null"); + } + inv = new ItemStack[toCopy.getSize()]; + setContents(toCopy.getContents()); + } + + public JInventory(PlayerInventory toCopy, boolean canStack) { + if (toCopy == null) { + throw new NullPointerException("PlayerInventory cannot be null"); + } + inv = new ItemStack[toCopy.getSize()]; + setContents(toCopy.getContents()); + allowExtraStack = canStack; + } + + public JInventory(ItemStack[] toCopy) { + inv = new ItemStack[toCopy.length]; + setContents(toCopy); + } + + public JInventory(ItemStack[] toCopy, boolean canStack) { + inv = new ItemStack[toCopy.length]; + setContents(toCopy); + allowExtraStack = canStack; + } + + public void setExtraStacking(boolean canStack) { + allowExtraStack = canStack; + } + + public final void setContents(ItemStack[] toCopy) { + if (toCopy == null || toCopy.length != inv.length) { + throw new IllegalArgumentException("cannot copy array of differing size"); + } + for (int i = 0; i < inv.length; ++i) { + inv[i] = toCopy[i] == null ? null : toCopy[i].clone(); + } + } + + public int size() { + return inv.length; + } + + public ItemStack[] getContents() { + return inv; + } + + public ItemStack get(int i) { + if (i < 0 || i >= inv.length) { + throw new ArrayIndexOutOfBoundsException("Index out of range: " + i); + } + return inv[i]; + } + + public void set(int i, ItemStack it) { + if (i < 0 || i >= inv.length) { + throw new ArrayIndexOutOfBoundsException("Index out of range: " + i); + } + inv[i] = it; + } + + public int firstEmpty() { + for (int i = 0; i < inv.length; ++i) { + if (inv[i] == null) { + return i; + } + } + return -1; + } + + public int find(ItemStack it) { + return it == null ? firstEmpty() : find(it.getTypeId(), it.getDurability()); + } + + public int find(ItemStack it, boolean exact) { + if (!exact) { + return it == null ? firstEmpty() : find(it.getTypeId(), it.getDurability()); + } else { + for (int i = 0; i < inv.length; ++i) { + if (inv[i] != null + && inv[i].getTypeId() == it.getTypeId() + && inv[i].getDurability() == it.getDurability() + && inv[i].getEnchantments().equals(it.getEnchantments())) { + return i; + } + } + return -1; + } + } + + public int find(Material m) { + return m == null ? firstEmpty() : find(m.getId(), (short) 0); + } + + public int find(Material m, short d) { + return m == null ? firstEmpty() : find(m.getId(), d); + } + + public int find(int id) { + return find(id, (short) 0); + } + + public int find(int id, short d) { + for (int i = 0; i < inv.length; ++i) { + if (inv[i] != null + && inv[i].getTypeId() == id + && (!JItems.hasData(id) || inv[i].getDurability() == d)) { + return i; + } + } + return -1; + } + + public ItemStack getFirstItem(ItemStack it) { + if(it == null) { + throw new NullPointerException("ItemStack cannot be null"); + } + int i = find(it.getTypeId(), it.getDurability()); + return i > -1 ? inv[i] : null; + } + + public ItemStack getFirstItem(ItemStack it, boolean exact) { + if(it == null) { + throw new NullPointerException("ItemStack cannot be null"); + } + int i = exact ? find(it, true) : find(it.getTypeId(), it.getDurability()); + return i > -1 ? inv[i] : null; + } + + public ItemStack getFirstItem(Material m) { + return getFirstItem(m.getId(), (short) 0); + } + + public ItemStack getFirstItem(Material m, short d) { + return getFirstItem(m.getId(), d); + } + + public ItemStack getFirstItem(int id) { + return getFirstItem(id, (short) 0); + } + + public ItemStack getFirstItem(int id, short d) { + int i = find(id, d); + return i > -1 ? inv[i] : null; + } + + public boolean contains(ItemStack it) { + if(it == null) { + throw new NullPointerException("ItemStack cannot be null"); + } + return find(it.getTypeId(), it.getDurability()) > -1; + } + + public boolean contains(ItemStack it, boolean exact) { + if(it == null) { + throw new NullPointerException("ItemStack cannot be null"); + } + return (exact ? find(it, true) : find(it.getTypeId(), it.getDurability())) > -1; + } + + public boolean contains(Material m) { + return contains(m.getId(), (short) 0); + } + + public boolean contains(Material m, short d) { + return contains(m.getId(), d); + } + + public boolean contains(int id) { + return contains(id, (short) 0); + } + + public boolean contains(int id, short d) { + return find(id, d) > -1; + } + + public int count() { + int c = 0; + for (int i = 0; i < inv.length; ++i) { + if (inv[i] != null) { + c += inv[i].getAmount(); + } + } + return c; + } + + public int count(Material m) { + return m == null ? countFree() : count(m.getId(), (short) 0); + } + + public int count(Material m, short d) { + return m == null ? countFree() : count(m.getId(), d); + } + + public int count(int id) { + return count(id, (short) 0); + } + + public int count(int id, short d) { + int c = 0; + for (int i = 0; i < inv.length; ++i) { + if (inv[i] != null && inv[i].getTypeId() == id + && (!JItems.hasData(id) || inv[i].getDurability() == d)) { + c += inv[i].getAmount(); + } + } + return c; + } + + public int countFree() { + int c = 0; + for (int i = 0; i < inv.length; ++i) { + if (inv[i] == null || inv[i].getAmount() == 0) { + ++c; + } + } + return c; + } + + public int countFree(Material m) { + return m == null ? countFree() : countFree(m.getId(), (short) 0); + } + + public int countFree(Material m, short d) { + return m == null ? countFree() : countFree(m.getId(), d); + } + + public int countFree(int id) { + return countFree(id, (short) 0); + } + + public int countFree(int id, short d) { + int c = 0; + int max = allowExtraStack && JItems.isStackable(id) ? 64 : JItems.getMaxStack(id, d); + for (int i = 0; i < inv.length; ++i) { + if (inv[i] == null || inv[i].getAmount() == 0) { + c += max; + } else if (inv[i].getAmount() < max + && inv[i].getTypeId() == id + && (!JItems.hasData(id) || inv[i].getDurability() == d)) { + c += max - inv[i].getAmount(); + } + } + return c; + } + + public void condense() { + throw new UnsupportedOperationException("Not supported yet."); + } + + // condense with enchantment checks & not stacking items that shouldn't stack + public void safeCondense() { + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/src/me/jascotty2/lib/bukkit/item/CraftRecipe.java b/src/me/jascotty2/lib/bukkit/item/CraftRecipe.java new file mode 100644 index 0000000..b70eb9a --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/CraftRecipe.java @@ -0,0 +1,234 @@ +/** +/** + * Copyright (C) 2011 Jacob Scott + * Description: used to store the items used to craft another + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.io.CheckInput; +import java.util.LinkedList; + +public class CraftRecipe { // extends Kit + + protected LinkedList items = new LinkedList(); + public int resultAmount; + //public JItem resultItem; + + public CraftRecipe() { + } // end default constructor + + public CraftRecipe(String recipeStr) { + SetRecipe(fromStr(recipeStr)); + } + + public void AddItem(JItem toAdd) { + if (toAdd != null) { + items.add(new Kit(toAdd)); + } + } + + public void AddItem(JItem toAdd, int numUsed) { + if (toAdd != null && numUsed > 0) { + items.add(new Kit(toAdd, numUsed)); + } + } + + public void AddItem(JItems toAdd) { + if (toAdd != null) { + items.add(new Kit(new JItem(toAdd))); + } + } + + public void AddItem(JItems toAdd, int numUsed) { + if (toAdd != null && numUsed > 0) { + items.add(new Kit(new JItem(toAdd), numUsed)); + } + } + + public void AddItem(int id) { + if (id > 0) { + JItem i = new JItem(); + i.itemId = id; + items.add(new Kit(i)); + } + } + + public void AddItem(int id, byte dat) { + if (id > 0) { + JItem i = new JItem(); + i.itemId = id; + i.itemDat = dat; + items.add(new Kit(i)); + } + } + public void AddItem(int id, int numUsed) { + if (id > 0 && numUsed > 0) { + JItem i = new JItem(); + i.itemId = id; + items.add(new Kit(i, numUsed)); + } + } + + public void AddItem(int id, byte dat, int numUsed) { + if (id > 0 && numUsed > 0) { + JItem i = new JItem(); + i.itemId = id; + i.itemDat = dat; + items.add(new Kit(i, numUsed)); + } + } + + /** + * adds without checking if is a valid item + * @param toAdd + */ + public void AddNewItem(JItem toAdd) { + if (toAdd != null) { + Kit nk = new Kit(); + nk.AddNewItem(toAdd); + items.add(nk); + } + } + + /** + * adds without checking if is a valid item + * @param toAdd + * @param numUsed + */ + public void AddNewItem(JItem toAdd, int numUsed) { + if (toAdd != null && numUsed > 0) { + Kit nk = new Kit(); + nk.AddNewItem(toAdd, numUsed); + items.add(nk); + } + } + + public final void SetRecipe(CraftRecipe copy) { + items.clear(); + if (copy != null) { + items.addAll(copy.items); + } + } + + public final void SetRecipe(String craftStr) { + items.clear(); + if (craftStr != null) { + CraftRecipe n = fromStr(craftStr); + if (n != null) { + items.addAll(n.items); + } + } + } + + public static CraftRecipe fromStr(String craftStr) { + if (craftStr == null) { + return null; + } + // ex: 4@8+263=8 + CraftRecipe ret = new CraftRecipe(); + + // get result amount + if (craftStr.contains("=")) { + if (craftStr.split("=").length > 2 || craftStr.length() == craftStr.indexOf("=")) { + return null; + } + ret.resultAmount = CheckInput.GetInt(craftStr.substring(craftStr.indexOf("=") + 1), 0); + craftStr = craftStr.substring(0, craftStr.indexOf("=")); + } else { + ret.resultAmount = 1; + } + // extract all items + for (String i : craftStr.split("\\+")) { + JItem ni = null; + String it = i; + if (i.contains("@")) { + if (i.length() > i.indexOf("@")) { + it = i.substring(0, i.indexOf("@")); + ni = JItemDB.findItem(it); + ret.AddItem(ni, + CheckInput.GetInt(i.substring(i.indexOf("@") + 1), 0)); + } else { + it = i.substring(0, i.indexOf("@")); + ni = JItemDB.findItem(it); + ret.AddItem(ni); + } + } else { + ni = JItemDB.findItem(i); + ret.AddItem(ni); + } + if (ni == null) { + System.out.println("null item: " + it); + } + } + return ret.totalItems() == 0 ? null : ret; + } + + // from kit class + public int numItems() { + return items.size(); + } + + public int totalItems() { + int n = 0; + for (Kit k : items) { + n += k.totalItems(); + } + return n; + } + + public JItem getItem(int index) { + if (index >= 0 && index < items.size()) { + Kit k = items.get(index); + return new JItem(k.ID(), k.Name()); + } + return null; + } + + public int getItemCount(int index) { + if (index >= 0 && index < items.size()) { + return items.get(index).totalItems(); + } + return 0; + } + + public int getItemCount(JItem i) { + for (Kit k : items) { + if (k.ID() == i.ID()) { //if (((JItem) k).equals(i)) { + return k.totalItems(); + } + } + return 0; + } + + public JItem[] getItems() { + return items.toArray(new JItem[0]); + } + + public Kit[] getKits() { + return items.toArray(new Kit[0]); + } + + public Kit.KitItem[] getKitItems() { + Kit.KitItem[] ret = new Kit.KitItem[numItems()]; + for (int i = 0, n = 0; i < items.size(); ++i) { + for (int j = 0; j < items.get(i).numItems(); ++j, ++n) { + ret[n] = items.get(i).getKitItem(j); + } + } + return ret; + } +} // end class CraftRecipe + diff --git a/src/me/jascotty2/lib/bukkit/item/CreatureItem.java b/src/me/jascotty2/lib/bukkit/item/CreatureItem.java new file mode 100644 index 0000000..34b30fa --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/CreatureItem.java @@ -0,0 +1,308 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for selling creatures through a shop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.util.Str; +//import com.jynxdaddy.wolfspawn_04.UpdatedWolf; +//import org.bukkit.entity.Wolf; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Location; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityTargetEvent; + +/** + * @author jacob + */ +public class CreatureItem extends JItem { + + protected EntityType type; + + private static EntityType[] entities; + + public CreatureItem(EntityType creature) { + this.type = creature; + super.name = Str.titleCase(creature.getName()); + super.itemId = 4000 + type.ordinal(); + } + + public CreatureItem(EntityType creature, String name) { + this.type = creature; + super.name = name; + super.itemId = 4000 + type.ordinal(); + } + + public static void init() { + // all entities, beginning in 1.0 + entities = new EntityType[] { + EntityType.CHICKEN, EntityType.COW, + EntityType.CREEPER, EntityType.GHAST, + EntityType.GIANT, + EntityType.PIG, EntityType.PIG_ZOMBIE, + EntityType.SHEEP, EntityType.SKELETON, + EntityType.SLIME, EntityType.SPIDER, + EntityType.SQUID, EntityType.ZOMBIE, + EntityType.WOLF, + EntityType.CAVE_SPIDER, EntityType.ENDERMAN, EntityType.SILVERFISH, + EntityType.ENDER_DRAGON, EntityType.VILLAGER, + EntityType.BLAZE, EntityType.MUSHROOM_COW, + EntityType.MAGMA_CUBE, EntityType.SNOWMAN, + + EntityType.WITHER, EntityType.BAT, EntityType.WITCH, EntityType.VILLAGER + + }; + // add new entities, and allow backwards-compatibility +// try { +// EntityType t = EntityType.UNKNOWN; +// // success, add to array +// ordered = ArrayManip.arrayConcat(ordered, +// new EntityType[]{EntityType.UNKNOWN}); +// } catch (Throwable t) { +// } + } + + public static EntityType[] getCreatures() { + return entities; + } + + public static EntityType[] getNewEntities() { + if(OldEntityType.values().length == EntityType.values().length) { + return new EntityType[0]; + } + ArrayList unknown = new ArrayList(); + for(EntityType e : EntityType.values()) { + if(OldEntityType.fromId(e.getTypeId()) == null + && e.isAlive() + && e.getName() != null) { + unknown.add(e); + } + } + return unknown.toArray(new EntityType[0]); + } + +// public final void setID(int id) { +// if (id < CreatureType.values().length) { +// type = CreatureType.values()[id]; +// } else if (id >= 4000 && id < 4000 + CreatureType.values().length) { +// type = CreatureType.values()[id - 4000]; +// } else { +// type = CreatureType.CHICKEN; +// } +// } +// public final void SetEntity(CreatureItem copy) { +// //itemAliases.clear(); +// if (copy == null) { +// this.type = CreatureType.CHICKEN; +// name = type.getName(); +// } else { +// //this.itemAliases.addAll(copy.itemAliases); +// this.type = copy.type; +// this.name = copy.name; +// } +// } + public boolean equals(CreatureItem e) { + return type == e.type; + } + + public boolean equals(EntityType e) { + return type == e; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof CreatureItem) { + return equals((CreatureItem) obj); + } else if (obj instanceof JItem) { + return equals((JItem) obj); + } + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + this.type.ordinal(); + return hash; + } + +// public static CreatureItem getCreature(String search) { +// search = search.trim().toLowerCase(); +// ///for (CreatureItem c : CreatureItem.values()) { +// for (CreatureType c : CreatureType.values()) { +// if (c.toString().equalsIgnoreCase(search) +// || (creatureAliases.containsKey(c.ordinal()) +// && creatureAliases.get(c.ordinal()).contains(search))) { +// return new CreatureItem(c); +// } +// } +// return null; +// } +// public static boolean creatureExists(String search) { +//// for (CreatureType c : CreatureType.values()) { +//// if (c.toString().equalsIgnoreCase(search) +//// || (creatureAliases.containsKey(c.ordinal()) +//// && creatureAliases.get(c.ordinal()).contains(search))) { +//// return true; +//// } +//// +//// } +// return getCreature(search) != null; +// } + public static boolean creatureExists(int search) { + if (search >= 4000) { + search -= 4000; + } + return search >= 0 && search < entities.length; + } + + public static CreatureItem getCreature(int search) { + if (search >= 4000) { + search -= 4000; + } + return search >= 0 && search < entities.length + ? new CreatureItem(entities[search]) + : new CreatureItem(EntityType.CHICKEN); + } + + public static EntityType getCreatureType(int search) { + if (search >= 4000) { + search -= 4000; + } + return search >= 0 && search < entities.length + ? entities[search] + : EntityType.CHICKEN; + } + + public void spawnNewWithOwner(Player owner) { + //CreatureType toSpawn + Location loc = owner.getLocation(); + + Entity e = owner.getWorld().spawnEntity(loc, type);//.spawnCreature(loc, type); + if (e instanceof Creature){ //type == CreatureType.SLIME || type == CreatureType.GHAST) { + addFriends(owner, e); + } else { + Creature creature = (Creature) e; + + if (creature instanceof Wolf) { + ((Wolf) creature).setOwner(owner); + creature.setHealth(20); + } else { + addFriends(owner, creature); + } + } + + //MonsterTamer.writeUsers(); + } + + public static void spawnNewWithOwner(Player owner, EntityType toSpawn) { + + Location loc = owner.getLocation(); + + LivingEntity c = owner.getWorld().spawnCreature(loc, toSpawn); + + if (c instanceof Wolf) { + ((Wolf) c).setOwner(owner); + c.setHealth(20); + //UpdatedWolf w = new UpdatedWolf((Wolf) c); + //Logger.getAnonymousLogger().info(String.valueOf(w.getHandle().health) + w.getHandle().y()); + //w.setOwner(owner.getName()); + //Logger.getAnonymousLogger().info("spawning owner = " + owner.getName()); + //Logger.getAnonymousLogger().info(w.toString()); + //Logger.getAnonymousLogger().info(String.valueOf(w.getHandle().health) + w.getHandle().y()); + addFriends(owner, c); + } else if (c instanceof Creature) { + Creature creature = (Creature) c; + //Logger.getAnonymousLogger().info("spawning " + toSpawn); + addFriends(owner, creature); + } + } + // revised from MonsterTamer code: + // player name, list of monster entity ids + public static ConcurrentHashMap> friends = new ConcurrentHashMap>(); + // list of friendly entity ids + public static ArrayList friendlies = new ArrayList(); + // entity id, attacking name + public static ConcurrentHashMap targets = new ConcurrentHashMap(); + + public static void addFriends(Player p, Entity c) { + //ArrayList array = friends.containsKey(p.getName()) ? friends.get(p.getName()) : new ArrayList(); + if (friends.containsKey(p.getName())) { + friends.get(p.getName()).add(c.getEntityId()); + } else { + ArrayList array = new ArrayList(); + array.add(c.getEntityId()); + friends.put(p.getName(), array); + } + friendlies.add(c.getEntityId()); + return; + } + + public static class EntityListen implements Listener { + + @EventHandler + public void onEntityDamage(EntityDamageEvent event) { + if ((event.getCause() == DamageCause.FIRE_TICK || event.getCause() == DamageCause.FIRE) + && friendlies.contains(event.getEntity().getEntityId())) { + event.setCancelled(true); + event.getEntity().setFireTicks(0); + } else if (event instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event; + if (e.getDamager() instanceof LivingEntity + && e.getEntity() instanceof Player + && friends.get(((Player) e.getEntity()).getName()) != null) { + ArrayList array = friends.get(((Player) e.getEntity()).getName()); + List livingEntities = e.getEntity().getWorld().getLivingEntities(); + for (LivingEntity i : livingEntities) { + if (i instanceof Creature + && array.contains(i.getEntityId())) { + ((Creature) i).setTarget((LivingEntity) e.getDamager()); + } + } + } + } + } + + @EventHandler + public void onEntityTarget(EntityTargetEvent e) { + if ((e.getTarget() instanceof Player)) { + Player p = (Player) e.getTarget(); + if (friendlies.contains(e.getEntity().getEntityId())) { + String name = targets.get(e.getEntity().getEntityId()); + if (name == null || name.isEmpty()) { + e.setCancelled(true); + return; + } + if (!(name.equals(p.getName()))) { + e.setCancelled(true); + return; + } + } + } + } + } +} // end class CreatureItem + diff --git a/src/me/jascotty2/lib/bukkit/item/ItemStockEntry.java b/src/me/jascotty2/lib/bukkit/item/ItemStockEntry.java new file mode 100644 index 0000000..1a5c945 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/ItemStockEntry.java @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: and entry to define an item's stock amount + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import java.util.Date; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class ItemStockEntry { + + public int itemNum, itemSub; + public long amount; + public String name; + protected Date tempCacheDate = null; // when last updated + + public ItemStockEntry() { + name = ""; + } // end default constructor + + public ItemStockEntry(int id, int sub, String itemName, long amt) { + itemNum = id; + itemSub = sub; + name = itemName; + amount = amt; + } + + public ItemStockEntry(JItem toAdd, long amt) { + Set(toAdd); + amount = amt; + } + + public ItemStockEntry(ItemStockEntry toCopy) { + Set(toCopy); + } + + public ItemStockEntry(JItem toCopy) { + Set(toCopy); + } + + public ItemStockEntry(ItemStack toCopy) { + Set(toCopy); + } + + public final void Set(ItemStockEntry toCopy) { + itemNum = toCopy.itemNum; + itemSub = toCopy.itemSub; + name = toCopy.name; + amount = toCopy.amount; + tempCacheDate = new Date(); + } + + public final void Set(JItem toCopy) { + if (toCopy != null) { + if (toCopy.item != null) { + itemNum = toCopy.item.ID(); + itemSub = toCopy.item.Data(); + name = toCopy.item.getName(); + } else { + itemNum = toCopy.itemId; + itemSub = toCopy.itemDat; + name = toCopy.name; + } + } else { + itemNum = -1; + itemSub = 0; + name = "null"; + } + amount = 0; + tempCacheDate = new Date(); + } + + public final void Set(ItemStack toCopy) { + itemNum = toCopy.getTypeId(); + JItem copy = JItemDB.findItem(toCopy); + if (copy != null) { + if (copy.item != null) { + itemSub = copy.item.Data(); + name = copy.item.getName(); + } else { + itemSub = copy.itemDat; + name = copy.name; + } + } else { + itemSub = toCopy.getDurability(); + name = toCopy.getData().getItemType().name(); + } + amount = toCopy.getAmount(); + tempCacheDate = new Date(); + } + + public void SetAmount(long amt) { + amount = amt; + tempCacheDate = new Date(); + } + + public void AddAmount(long amt) { + if (amt >= 0 || amount + amt > 0) { + amount += amt; + tempCacheDate = new Date(); + } + } + + public void RemoveAmount(long amt) { + amount -= amt; + tempCacheDate = new Date(); + } + + public long getTime() { + return tempCacheDate == null ? 0 : tempCacheDate.getTime(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ItemStockEntry) { + return equals((ItemStockEntry) o); + } else if (o instanceof JItem) { + return equals((JItem) o); + } + return false; + } + + public boolean equals(JItem i) { + if (i == null || i.item == null || itemNum != i.ID()) { + return false; + } + return itemSub == i.Data() || i.IsTool(); + } + + public boolean equals(ItemStockEntry i) { + if (i == null || itemNum != i.itemNum) { + return false; + } + if (itemSub == i.itemSub) { + return true; + } + JItem t = JItemDB.GetItem(i.itemNum, (byte) i.itemSub); + return t != null && t.IsTool(); + } + + public boolean equals(ItemStack i) { + if (i == null || itemNum != i.getTypeId()) { + return false; + } + if (itemSub == i.getDurability()) { + return true; + } + JItem t = JItemDB.findItem(i); + return t != null && t.IsTool(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 11 * hash + this.itemNum; + hash = 11 * hash + this.itemSub; + hash = 11 * hash + (int) (this.amount ^ (this.amount >>> 32)); + return hash; + } + + @Override + public String toString() { + return String.format("%d:%d (%s) %d", itemNum, itemSub, name, amount); + } +} // end class ItemStockEntry + diff --git a/src/me/jascotty2/lib/bukkit/item/JItem.java b/src/me/jascotty2/lib/bukkit/item/JItem.java new file mode 100644 index 0000000..1ef2906 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/JItem.java @@ -0,0 +1,476 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: class for defining an item + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.bukkit.MinecraftChatStr; +import me.jascotty2.lib.util.Str; +import java.util.ArrayList; +import java.util.LinkedList; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Item; +import org.bukkit.inventory.ItemStack; + +public class JItem { + + //protected static int MAX_LEVENSHTEIN_DIST = 1; + protected JItems item = null; + public String color = null; // color is used if this item has a custom color + // data when masquerading something as an item + protected boolean legal = true; + protected int itemId = -1; + protected int maxStack = 0; // so far, only set if init with Material or JItems + protected short itemDat = 0; + protected String name = null; + private LinkedList aliases = new LinkedList(); + private LinkedList subAliases = new LinkedList(); + private LinkedList categories = new LinkedList(); + private LinkedList recipes = new LinkedList(); + + public JItem() { + } + + public JItem(int id) { + item = JItems.getItem(id); + setInf(id); + } + + public JItem(int id, short data) { + item = JItems.getItem(id, data); + itemDat = data; + setInf(id, data); + } + + public JItem(int id, String name) { + item = JItems.getItem(id); + setInf(id); + this.name = name; + } + + public JItem(int id, short data, String name) { + item = JItems.getItem(id, data); + setInf(id, data); + this.name = name; + } + + public JItem(JItems copy) { + item = copy; + setInf(); + } + + public JItem(Material copy) { + if (copy != null) { + item = JItems.getItem(copy); + setInf(copy.getId()); + if (item == null) { + maxStack = copy.getMaxStackSize(); + } + } else { + setInf(); + } + if (copy != null) { + name = Str.titleCase(copy.name().replace("_", " ")); + //System.out.println(name + " (" + copy.getId() + ") max: " + copy.getMaxStackSize()); + } + } + + public JItem(JItem other) { + copy(other); + } + + public JItem(Kit copy) { + if (copy != null) { + this.name = copy.name; + color = copy.color; + itemId = copy.itemId; + legal = copy.legal; + } + } + + private void setInf() { + itemId = item == null ? -1 : item.ID(); + itemDat = item == null ? 0 : item.Data(); + legal = item == null ? true : item.IsLegal(); + name = item == null ? "null" : item.getName(); + maxStack = item == null ? 0 : item.MaxStackSize(); + } + + private void setInf(int id) { + setInf(id, (short) 0); + } + + private void setInf(int id, short dat) { + if (item != null) { + itemId = item.ID(); + itemDat = item.Data(); + legal = item.IsLegal(); + maxStack = item.MaxStackSize(); + } else { + itemId = id; + itemDat = dat; + } + } + + protected final void copy(JItem copy) { + if (copy != null) { + item = copy.item; + color = copy.color; + legal = copy.legal; + name = copy.name; + itemId = copy.itemId; + itemDat = copy.itemDat; + maxStack = copy.maxStack; + + aliases = copy.aliases; + subAliases = copy.subAliases; + categories = copy.categories; + recipes = copy.recipes; + } + } + + public int ID() { + return item == null ? itemId : item.ID(); + } + + public short Data() { + return item == null ? itemDat : item.Data(); + } + + public short MaxDamage() { + return item == null ? 0 : item.MaxDamage(); + } + + public boolean IsValidItem() { + return item != null && item.ID() != 0; + } + + public boolean IsTool() { + return item == null ? false : item.IsTool(); + } + + public boolean IsLegal() { + return item == null ? legal : item.IsLegal(); + } + + public int MaxStackSize() { + return item == null ? 0 : item.MaxStackSize(); + } + + public String IdDatStr() { + return item == null ? String.format("%d:%d", itemId, (int) itemDat) : item.IdDatStr(); + } + + public String Name() { + return item == null ? name : item.getName(); + } + + public void SetItem(JItem copy) { + if (copy == null) { + item = null; + color = null; + itemId = -1; + legal = true; + } else { + item = copy.item; + color = copy.color; + itemId = copy.itemId; + legal = copy.legal; + } + } + + public void SetItem(JItems copy) { + item = copy; + color = null; + setInf(); + } + + public void SetItem(int id, short data) { + SetItem(JItems.getItem(id, data)); + } + + public void AddAlias(String a) { + if (a != null) { + aliases.add(a.trim().toLowerCase()); + } + } + + public void AddSubAlias(String a) { + if (a != null) { + subAliases.add(a.trim().toLowerCase()); + } + } + + public void AddCategory(String a) { + if (a != null) { + categories.add(a.trim().toLowerCase()); + } + } + + public void AddRecipe(String craft) { + System.out.println("adding " + craft); + CraftRecipe toAdd = CraftRecipe.fromStr(craft); + if (toAdd != null) { + recipes.add(toAdd); + } else { + java.util.logging.Logger.getAnonymousLogger().log(java.util.logging.Level.WARNING, + String.format("(class error) %s has an invalid item or craft syntax error: %s", name, craft)); + } + } + + public void AddRecipe(CraftRecipe toAdd) { + if (toAdd != null) { + recipes.add(toAdd); + } + } + + public boolean HasAlias(String a) { + return a == null ? false : aliases.contains(a.trim().toLowerCase()); +// if (a != null) { +// a = a.trim().toLowerCase(); +// if (aliases.contains(a)) { +// return true; +// } else { +// for (String al : aliases) { +// if (Str.getLevenshteinDistance(a, al) <= MAX_LEVENSHTEIN_DIST) { +// return true; +// } +// } +// } +// } +// return false; + } + + public boolean HasSubAlias(String a) { + return a == null ? false : subAliases.contains(a.trim().toLowerCase()); +// if (a != null) { +// a = a.trim().toLowerCase(); +// if (subAliases.contains(a)) { +// return true; +// } else { +// for (String al : subAliases) { +// if (Str.getLevenshteinDistance(a, al) <= MAX_LEVENSHTEIN_DIST) { +// return true; +// } +// } +// } +// } +// return false; + } + + public boolean HasCategory(String a) { + return a == null ? false : categories.contains(a.trim().toLowerCase()); + } + + public ArrayList Aliases() { + return new ArrayList(aliases); + } +// public static JItem findItem(int id) { +// JItems i = JItems.getItem(id); +// return i == null ? null : new JItem(i); +// } +// +//// public static JItem findItem(JItem it) { +//// JItems i = JItems.getItem(it.ID(), it.Data()); +//// return i == null ? null : new JItem(i); +//// } +// public static JItem findItem(ItemStack search) { +// if (search == null) { +// return null; +// } +// return findItem(search.getTypeId() + ":" + search.getDurability()); +// } +// +// public static JItem findItem(ItemStockEntry search) { +// if (search == null) { +// return null; +// } +// return findItem(search.itemNum + ":" + search.itemSub); +// } +// +// public static JItem findItem(Item search) { +// JItems i = JItems.findItem(search); +// return i == null ? null : new JItem(i); +// } + +// public static JItem findItem(int id, short sub) { +// JItems i = JItems.getItem(id, sub); +// return i == null ? null : new JItem(i); +// } +// +// public static JItem findItem(String search) { +// JItems i = JItems.findItem(search); +// //System.out.println("searching for " + search + " " + (i == null ? "(not found)" : i)); +// return i == null ? null : new JItem(i); +// } +// +// public static JItem[] findItems(String search) { +// JItems its[] = JItems.findItems(search); +// JItem ret[] = new JItem[its.length]; +// for (int i = 0; i < its.length; ++i) { +// ret[i] = new JItem(its[i]); +// } +// return ret; +// } + public String coloredName() { + return (color == null ? "" : color) + (item == null ? name : item.getName()); + } + + public boolean SetColor(String col) { + if (col == null) { + color = null; + } + color = MinecraftChatStr.getChatColorStr(col, ChatColor.WHITE); + return color.length() > 0; + } + + public boolean nameMatch(String str) { + if (str != null) { + str = str.trim().toLowerCase(); + if ((item != null + && (item.getName().equalsIgnoreCase(str) || HasAlias(str))) + || (name != null && name.equalsIgnoreCase(str))) { + return true; + } + if (item != null && str.contains(":")) { + String s1 = str.substring(0, str.indexOf(":")), + s2 = str.substring(str.indexOf(":") + 1); + return (name.equalsIgnoreCase(s1) || HasAlias(s1)) + && HasSubAlias(s2); + } + } + return false; + } + + public boolean equals(JItem i) { + return i != null + && (item != null ? i.item != null && item == i.item + : itemId == i.itemId && itemDat == i.itemDat); + } + + public boolean equals(JItems i) { + return item == i; + } + + public boolean equals(Item i) { + return i != null && this.equals(i.getItemStack()); + } + + public boolean equals(ItemStack i) { + return i != null && (item != null ? item.equals(i) + : itemId == i.getTypeId() && (IsTool() || itemDat == i.getDurability())); + } + + public boolean equals(ItemStockEntry i) { + return i != null && (item != null ? item.ID() == i.itemNum && item.Data() == i.itemSub + : itemId == i.itemNum && (IsTool() || itemDat == i.itemSub)); + } + + public boolean equals(String s) { + return s != null + && ((item != null ? item.getName() : name).replace(" ", ""). + equalsIgnoreCase(s.replace(" ", "")) + /*|| Str.getLevenshteinDistance(s.replace(" ", ""), + (item != null ? item.getName() : name).replace(" ", "")) <= MAX_LEVENSHTEIN_DIST*/); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } +// if (obj instanceof String) { +// return equals((String) obj); +// } +// else + if (obj instanceof JItem) { + return equals((JItem) obj); + } else if (obj instanceof Item) { + return equals((Item) obj); + } else if (obj instanceof ItemStack) { + return equals((ItemStack) obj); + } else if (obj instanceof ItemStockEntry) { + return equals((ItemStockEntry) obj); + } + return false; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 47 * hash + (this.item != null ? this.item.hashCode() : 0); + return hash; + } + + @Override + public String toString() { + return item == null ? (name == null ? "" : name) : item.toString(); + } + + public ItemStack toItemStack() { + return item == null + ? (itemDat <= Byte.MAX_VALUE ? new ItemStack(itemId, 1, (short) itemDat) : new ItemStack(itemId, 1, itemDat)) + : (isEntity() ? null : item.toItemStack()); + } + + public ItemStack toItemStack(int amount) { + return item == null + ? (itemDat <= Byte.MAX_VALUE ? new ItemStack(itemId, amount, (short) itemDat) : new ItemStack(itemId, amount, itemDat)) + : (isEntity() ? null : item.toItemStack(amount)); + } + + // creatures are numbered starting at 4000 + public boolean isEntity() { + return item == null && itemId >= 4000 && itemId < 5000; + } + + // kits are numbered at 5000+ + public boolean isKit() { + return item == null ? itemId >= 5000 : item.ID() >= 5000; + } + + public int getMaxStackSize() { + return item == null ? maxStack : item.MaxStackSize(); + } + + protected void setMaxStack(int stack) { + maxStack = stack; + if (item != null) { + item.setMaxStack(stack); + } + } + + public static boolean contains(JItem[] items, ItemStack check) { + if (items != null) { + if (check == null) { + for (JItem i : items) { + if (i == null) { + return true; + } + } + } else { + for (JItem i : items) { + if (i != null && i.equals(check)) { + return true; + } + } + } + } + return false; + } +} // end class JItem + diff --git a/src/me/jascotty2/lib/bukkit/item/JItemDB.java b/src/me/jascotty2/lib/bukkit/item/JItemDB.java new file mode 100644 index 0000000..adb83e7 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/JItemDB.java @@ -0,0 +1,709 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: searchable database for items + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.io.CheckInput; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.lib.util.Str; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import me.jascotty2.lib.bukkit.config.Configuration; +import me.jascotty2.lib.bukkit.config.ConfigurationNode; +import org.bukkit.entity.EntityType; + +public class JItemDB { + + protected static int MAX_LEVENSHTEIN_DIST = 2; + private final static Logger logger = Logger.getLogger("Minecraft"); + protected static Map items = new HashMap(); + protected static Map kits = new HashMap(); + protected static List itemCategories = new ArrayList(); + protected static Map> itemCategoryItemlist = new HashMap>(); + protected static boolean dbLoaded = false; + + public static boolean load() { + return load(new File("."), "itemsdb.yml"); + } + + public static boolean load(File folder, String fname) { + return load(new File(folder, fname)); // "itemsdb.yml" + } + + public static boolean load(File itemFile) { + //if(!dbLoaded){ + loadDefaultItems(); + //} + if (itemFile != null && itemFile.exists()) { + try { + Configuration itemdb = new Configuration(itemFile); + itemdb.load(); + + ConfigurationNode n = itemdb.getNode("items"); + if (n == null) { + logger.log(Level.WARNING, String.format("\'items\' not found in %s", itemFile.getName())); + } else { + // will run through items twice: 1st load items, then the craft recipes + for (String k : itemdb.getKeys("items")) { + if (k.length() >= 5 && k.substring(0, 4).equalsIgnoreCase("item")) { + n = itemdb.getNode("items." + k); + if (n != null) { + JItem item = null; + if (k.indexOf("sub") > 0) { + //new JItem(); + item = GetItem(CheckInput.GetInt(k.substring(4, k.indexOf("sub")), -1), + CheckInput.GetByte(k.substring(k.indexOf("sub") + 3), (byte) 0)); + } else { + item = GetItem(CheckInput.GetInt(k.substring(4), -1)); + } + if (item != null) {// && item.item != null) { + + // can also have a sub-value alias (ex: wool:green) + String a = n.getString("sub"); + if (a != null) { + String all[] = a.split(","); + for (String i : all) { + item.AddSubAlias(i.trim()); + } + } + + a = n.getString("name"); + if (a != null) { + a = a.replace("'", "").replace("\"", ""); + item.name = a; + if (item.item != null) { + item.item.name = a; + } + } + + item.SetColor(n.getString("color")); + + if (n.getBoolean("legal", item.legal) != item.legal) { + item.legal = !item.legal; + } + + // now add aliases + a = n.getString("aliases"); + if (a != null) { + String all[] = a.split(","); + for (String i : all) { + item.AddAlias(i.trim().toLowerCase()); + } + } + + // now check for custom maxstack + if (n.getString("maxstack") != null) { + item.setMaxStack(n.getInt("maxstack", item.maxStack)); + } + + items.put(item.IdDatStr(), item); + } + //System.out.println("Added: " + item); + } + } + + } + // now load craft recipes + for (String k : itemdb.getKeys("items")) { + if (k.length() >= 5 && k.substring(0, 4).equalsIgnoreCase("item")) { + n = itemdb.getNode("items." + k); + if (n != null) { + JItem item = JItemDB.findItem(n.getString("name")); + String a = n.getString("craft"); + if (a != null && item != null) { + String all[] = a.split(","); + for (String i : all) { + CraftRecipe toAdd = CraftRecipe.fromStr(i.trim()); + if (toAdd != null) { + item.AddRecipe(toAdd); + } else { + logger.log(Level.WARNING, String.format("%s has an invalid item or craft syntax error: %s", item.toString(), i)); + } + } + } + } + } + } + } // end item loading + + n = itemdb.getNode("kits"); + if (n == null) { + logger.log(Level.WARNING, String.format("\'kits\' not found in %s", itemFile.getName())); + } else { + kits.clear(); + for (String k : itemdb.getKeys("kits")) { // for( : itemdb.getNodeList("", null)){ + if (k.length() >= 4 && k.substring(0, 3).equalsIgnoreCase("kit")) { + n = itemdb.getNode("kits." + k); + if (n != null) { + String a = n.getString("items"); + if (a != null) { + Kit kit = Kit.fromStr(a); + if (kit == null) { + logger.log(Level.WARNING, String.format("kit %s (%s) is an invalid item or has incorrect syntax", k, n.getString("name", "not named"))); + } else if (n.getString("name", "").length() == 0) { + logger.log(Level.WARNING, String.format("kit %s has no name", k)); + } else { + // kits are numbered starting at 5000 + kit.itemId = 4999 + CheckInput.GetInt(k.substring(3), 0); + if (kit.itemId <= 4999) { + logger.log(Level.WARNING, String.format("%s is an invalid kit number. (Must start at 1)", k)); + continue; //next kit node + } + kit.SetColor(n.getString("color")); + kit.legal = n.getBoolean("legal", true); + kit.name = n.getString("name", "null"); + // now add aliases (if any) + a = n.getString("aliases"); + if (a != null) { + String all[] = a.split(","); + for (String i : all) { + kit.AddAlias(i.trim().toLowerCase()); + } + } +// String o = ""; +// for(Kit.KitItem ki : kit.getKitItems()){ +// o+=ki.toString() + ", "; +// } +// System.out.println(o); + + // add to list + kits.put(kit.itemId, kit); + // add to item list + items.put(kit.IdDatStr(), kit); + + } + } + } + } + } + } // end kit loading + + n = itemdb.getNode("entities"); + if (n == null) { + logger.log(Level.WARNING, String.format("\'entities\' not found in %s", itemFile.getName())); + } else { + for (String k : itemdb.getKeys("entities")) { + if (k.length() >= 7 && k.substring(0, 6).equalsIgnoreCase("entity")) { + n = itemdb.getNode("entities." + k); + if (n != null) { + int eid = CheckInput.GetInt(k.substring(6), -1); + JItem citem = GetItem(eid + 4000); + if (eid >= 0 && citem != null) { + //CreatureItem citem = CreatureItem.getCreature(eid); + + // now add aliases + String a = n.getString("aliases"); + if (a != null) { + String all[] = a.split(","); + for (String i : all) { + citem.AddAlias(i.trim().toLowerCase()); + } + } + items.put(String.format("%d:0", citem.ID()), citem); + } + } + } + } + } + + String catErr = ""; + n = itemdb.getNode("categories"); + if (n == null) { + logger.log(Level.WARNING, String.format("\'categories\' not found in %s", itemFile.getName())); + } else { + itemCategories.clear(); + itemCategoryItemlist.clear(); + List cats = itemdb.getKeys("categories"); + for (String c : cats) { + String itms = n.getString(c); + if (c != null && itms != null) { + String list[] = itms.split(","); + ArrayList categoryItems = new ArrayList(); + for (String i : list) { + i = i.trim(); + if (i.length() > 0) { + JItem it = findItem(i); + if (it != null) { + it.AddCategory(c); + categoryItems.add(it); + } else { + catErr += i + ", "; + } + } + } + itemCategories.add(c); + itemCategoryItemlist.put(c, categoryItems); + } + } + if (catErr.length() > 0) { + logger.warning(String.format("Invalid item entries in %s categories: %s", itemFile.getName(), catErr.substring(0, catErr.length() - 2))); + } + } + //logger.log(Level.INFO, "Items loaded: " + items.size() + " + " + kits.size() + " kits"); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error loading itemsdb.yml", ex); + return dbLoaded = false; + } + return dbLoaded = true; + } + return false; + } + + /** + * clears items & loads with defaults + */ + private static void loadDefaultItems() { + items.clear(); + CreatureItem.init(); + int i = 0; + for (; i < CreatureItem.getCreatures().length; ++i) { + items.put((4000 + i) + ":0", new CreatureItem(CreatureItem.getCreatures()[i])); + } + // now check for existing entities that aren't in the ordered array + for (EntityType c : CreatureItem.getNewEntities()) { + items.put((4000 + (i++)) + ":0", new CreatureItem(c)); + System.out.println("New Entity: (" + (4000 + (i - 1)) + ") " + c.getName()); + } + // now add hard-coded entries, including subtypes + for (JItems it : JItems.values()) { + if (it.ID() >= 0) { + items.put(it.IdDatStr(), new JItem(it)); + } + } + // add all items in Material.values() aren't in JItems (upgrade-proof) + for (Material m : Material.values()) { + String idd = m.getId() + ":0"; + if (!items.containsKey(idd)) { + //System.out.println("new item: " + idd + " - " + m.name()); + items.put(idd, new JItem(m)); + } + } + } + + public boolean isLoaded() { + return dbLoaded; + } + + public static JItem GetItem(ItemStockEntry i) { + return i == null ? null : GetItem(i.itemNum, (byte) i.itemSub); + } + + public static JItem GetItem(ItemStack i) { + return i == null ? null : GetItem(i.getTypeId(), (i.getData() == null ? (byte) 0 : i.getData().getData())); + } + + public static JItem GetItem(int id) { + return GetItem(id, (byte) 0); + } + + public static JItem GetItem(int id, byte dat) { + if (JItems.isTool(id) || !JItems.hasData(id)) { + dat = 0; + } + String idd = id + ":" + dat; + if (items.containsKey(idd)) { + return items.get(idd); + } +// if (dat == 0) { +// for (Kit k : kits.values()) { +// if (k.itemId == id) { +// return k; +// } +// } +// +//// CreatureItem c = CreatureItem.getCreature(id); +//// if (c != null) { +//// return new JItem(c.ID(), c.name); +//// } +// } + return null; + } + + public static JItem findItem(ItemStack search) { + try { + return search == null ? null : GetItem(search.getTypeId(), (search.getData() == null ? (byte) 0 : search.getData().getData())); + } catch (Exception e) { + // recent bukkit fix.. + return GetItem(search.getTypeId(), (byte) 0); + } + } + + public static JItem findItem(String search) { + + if (search == null) { + return null; + } else if (CheckInput.IsInt(search)) { + return GetItem(CheckInput.GetInt(search, Integer.MIN_VALUE)); + } + + JItem it = null; + String s1 = search.contains(":") ? search.substring(0, search.indexOf(':')).trim() : search, + s2 = search.contains(":") ? search.substring(search.indexOf(':') + 1).trim() : ""; + + if (CheckInput.IsInt(s1)) { + it = GetItem(CheckInput.GetInt(s1, Integer.MIN_VALUE)); + } else { + for (JItem i : items.values()) { + if (i != null && (i.equals(s1) //&& ((i.name != null && i.name.replace(" ", "").equalsIgnoreCase(s1)) + || i.HasAlias(s1))) { + it = i; + break; + } + } + } + + if (it == null) { // if no imediate result: check plurality + for (String ss : new String[]{ + (s1.endsWith("s") ? s1.substring(0, s1.length() - 1) : null), + (s1.endsWith("es") ? s1.substring(0, s1.length() - 2) : null), + (s1.endsWith("ies") ? s1.substring(0, s1.length() - 3) + "y" : null)}) { + if (ss == null) { + break; + } + for (JItem i : items.values()) { + if (i != null && i.equals(ss) /* && i.name != null + && (i.name.replace(" ", "").equalsIgnoreCase(ss) || i.HasAlias(ss))*/) { + it = i; + break; + } + } + if (it != null) { + break; + } + } + } + +// if (it == null) { +// +// } + + if (it == null) { + // still no result: now do a string compare + JItem match = null; + int matchDist = MAX_LEVENSHTEIN_DIST + 1, numMatch = 0; + for (JItem i : items.values()) { + if (i != null) { // double-checking.. + int d = Str.getLevenshteinDistance(i.Name().replace(" ", ""), s1); + if (d < matchDist) { + match = i; + matchDist = d; + numMatch = 1; + } else if (d == matchDist) { + ++numMatch; + } + } + } + if (numMatch == 1) { + it = match; + } else { + // now check aliases + match = null; + matchDist = MAX_LEVENSHTEIN_DIST + 1; + numMatch = 0; + for (JItem i : items.values()) { + if (i != null) { // double-checking.. + int d = MAX_LEVENSHTEIN_DIST + 1; + for (String a : i.Aliases()) { + int d2 = Str.getLevenshteinDistance(a, s1); + if (d2 < d) { + d = d2; + } + } + if (d < matchDist) { + match = i; + matchDist = d; + numMatch = 1; + } else if (d == matchDist) { + ++numMatch; + } + } + } + } + } + + if (it != null) { + if (!it.IsTool() && s2.length() > 0) { + // now check second part + if (CheckInput.IsByte(s2)) { + byte dat = CheckInput.GetByte(s2, (byte) 0); + for (JItem i : items.values()) { + if (i.itemId == it.itemId && i.itemDat == dat) { + return i; + } + } + } + for (JItem i : items.values()) { + if (i.itemId == it.itemId && i.HasSubAlias(s2)) { + return i; + } + } + return null; + } else { + return it; + } + } + + return null; + } + + public static JItem[] findItems(String search) { + if (search == null) { + return null; + } + if (CheckInput.IsInt(search)) { + return new JItem[]{GetItem(CheckInput.GetInt(search, Integer.MIN_VALUE))}; + } else if (search.contains(":")) { + return new JItem[]{findItem(search)}; + } + ArrayList found = new ArrayList(); + search = search.trim().toLowerCase(); + + // run a name search + for (String ss : new String[]{ + search, + (search.endsWith("s") ? search.substring(0, search.length() - 1) : null), + (search.endsWith("es") ? search.substring(0, search.length() - 2) : null), + (search.endsWith("ies") ? search.substring(0, search.length() - 3) + "y" : null)}) { + if (ss == null) { + break; + } + + for (JItem i : items.values()) { + if (i != null && !found.contains(i) + && (i.equals(ss) /* + && (i.name != null && i.name.replace(" ", "").toLowerCase().contains(ss) + || i.HasAlias(ss) */ + || (i.HasCategory(ss)))) { + found.add(i); + } else { + for (String suba : i.Aliases()) { + if (suba.contains(ss) && !found.contains(i)) { + found.add(i); + break; + } + } + } + } + } + return found.toArray(new JItem[0]); + } + + public static JItem[] getItemsByCategory(String search) { + if (search == null) { + return null; + } + + ArrayList found = new ArrayList(); + search = search.trim().toLowerCase(); + // run a name search + for (String ss : new String[]{ + search, + (search.endsWith("s") ? search.substring(0, search.length() - 1) : null), + (search.endsWith("es") ? search.substring(0, search.length() - 2) : null), + (search.endsWith("ies") ? search.substring(0, search.length() - 3) + "y" : null)}) { + if (ss == null) { + break; + } + for (JItem i : items.values()) { + if ((i.HasCategory(search)) + && !found.contains(i)) { + found.add(i); + } + } + } + return found.toArray(new JItem[0]); + } + + public static String[] getCategories() { + return itemCategories.toArray(new String[0]); + } + + public static boolean isCategory(String s) { + return itemCategories.contains(s); + } + + public static boolean isCategory(String s, boolean permissive) { + if (!permissive) { + return itemCategories.contains(s); + } + for (String c : itemCategories) { + if (Str.getLevenshteinDistance(s.toLowerCase(), c.toLowerCase()) < 4) { + return true; + } + } + return false; + } + + public static String findCategory(String s) { + for (String c : itemCategories) { + if (Str.getLevenshteinDistance(s.toLowerCase(), c.toLowerCase()) < MAX_LEVENSHTEIN_DIST) { + return c; + } + } + return null; + } + + public static List getCategory(String cat) { + return itemCategoryItemlist.get(cat); + } + + public static String GetItemName(ItemStack search) { + if (search == null) { + return null; + } + return GetItemName(search.getTypeId(), search.getData() == null ? 0 : search.getData().getData()); + } + + public static String GetItemName(int id) { + return GetItemName(id, (byte) 0); + } + + public static String GetItemName(int id, byte dat) { + + for (JItem i : items.values()) { + if (i.ID() == id && i.Data() == dat) { + return i.Name(); + } + } + for (Kit k : kits.values()) { + if (k.ID() == id) { + return k.Name(); + } + } +// CreatureItem c = CreatureItem.getCreature(id); +// if (c != null) { +// return c.name; +// } + return null; + } + + public static String GetItemColoredName(ItemStack it) { + return it == null ? null : GetItemColoredName(it.getTypeId(), it.getData() == null ? 0 : it.getData().getData()); + } + + public static String GetItemColoredName(int id, byte dat) { + return GetItemColoredName(id, (int) dat); + } + + public static String GetItemColoredName(int id, int dat) { + + for (JItem i : items.values()) { + if (i.ID() == id && i.Data() == dat) { + return i.coloredName(); + } + } + for (Kit k : kits.values()) { + if (k.ID() == id) { + return k.coloredName(); + } + } +// CreatureItem c = CreatureItem.getCreature(id); +// if (c != null) { +// return c.name; +// } + return null; + } + + public static boolean isItem(String search) { + JItem temp = findItem(search); + return temp != null && temp.itemId < 5000; + } + + public static Kit getKit(String search) { + /*if (kits.containsKey(search)) { + return kits.get(search); + }*/ + for (Kit k : kits.values()) { + if (k.equals(search)) { + // debugging: output items in kit + /* + System.out.println("kit " + search + " found: "); + for (Kit.KitItem i : k.getKitItems()) { + System.out.println(i + "( " + i.itemAmount + " )"); + }//*/ + return k; + } + } + return null; + } + + public static Kit getKit(JItem search) { + /*if (!search.isKit()) { + return null; + }*/ + return kits.get(search.itemId); + } + + public static boolean isKit(String search) { + return getKit(search) != null; + } + + public static int size() { + return items.size(); + } + + /** + * can really only be run once... + * assigns a color to items that don't have one assigned + * @param col color string to set + */ + public static void setDefaultColor(String col) { + for (JItem i : items.values()) { + if (i.color == null || i.color.length() == 0) { + i.SetColor(col); + } + } + } +// public static void main(String[] args) { +// //JItemDB.load(new java.io.File("../../../../BetterShop/src/itemsdb.yml")); +// JItemDB.load(new java.io.File("/media/Data/Jacob/Programs/Java/Bukkit Plugins/BetterShop/src/itemsdb.yml")); +// me.jascotty2.lib.io.ConsoleInput c = new me.jascotty2.lib.io.ConsoleInput(); +// String in = ""; +// while ((in = c.GetString("\n> ")).length() > 0) { +// JItem i = findItem(in); +// if (i == null) { +// JItem is[] = JItemDB.getItemsByCategory(in); +// if (is != null && is.length > 0) { +// System.out.println("found in category: "); +// for (JItem i2 : is) { +// System.out.println(i2); +// } +// } else { +// in = c.GetString("not found.. input the id# ");//, 0, 10000); +// i = findItem(in); +// if (i == null) { +// System.out.println("invalid #"); +// } else { +// System.out.println(i + ": " + i.Aliases()); +// } +// } +// } else { +// System.out.println("found: " + in + "==" + i); +// for (JItem i2 : findItems(in)) { +// System.out.println("found2: " + i2); +// } +// } +// } +// } +} diff --git a/src/me/jascotty2/lib/bukkit/item/JItems.java b/src/me/jascotty2/lib/bukkit/item/JItems.java new file mode 100644 index 0000000..4f7831a --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/JItems.java @@ -0,0 +1,1011 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: enum to define items & information about them + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.io.CheckInput; +import java.util.Arrays; +import java.util.LinkedList; +import org.bukkit.Material; +import org.bukkit.entity.Item; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public enum JItems { + + AIR("Air", 0, false), + STONE("Stone", 1, "4@8+263=8, 4@3+5@2=3"), + GRASS("Grass", 2, false), + DIRT("Dirt", 3), + COBBLESTONE("Cobblestone", 4), + WOOD("Wood", 5, "17=4"), + SPRUCE_WOOD("Spruce Wood", 5, (short) 1, "17:1=4"), + BIRCH_WOOD("Birch Wood", 5, (short) 2, "17:2=4"), + JUNGLE_WOOD("Jungle Wood", 5, (short) 3, "17:3=4"), + SAPLING("Sapling", 6, (short) 0), + SPRUCE_SAPLING("Spruce Sapling", 6, (short) 1), + PINE_SAPLING("Pine Sapling", 6, (short) 2), + JUNGLE_SAPLING("Jungle Sapling", 6, (short) 3), + BEDROCK("Bedrock", 7, false), + WATER("Water", 8, false), + STATIONARY_WATER("Stationary Water", 9, false), + LAVA("Lava", 10, false), + STATIONARY_LAVA("Stationary Lava", 11, false), + SAND("Sand", 12), + GRAVEL("Gravel", 13), + GOLD_ORE("Gold Ore", 14), + IRON_ORE("Iron Ore", 15), + COAL_ORE("Coal Ore", 16, false), + LOG("Log", 17, (short) 0), + SPRUCE_LOG("Spruce Log", 17, (short) 1), + BIRCH_LOG("Birch Log", 17, (short) 2), + JUNGLE_LOG("Jungle Log", 17, (short) 3), + LEAVES("Leaves", 18, (short) 0), + SPRUCE_LEAVES("Spruce Leaves", 18, (short) 1), + BIRCH_LEAVES("Birch Leaves", 18, (short) 2), + JUNGLE_LEAVES("Jungle Leaves", 18, (short) 3), + SPONGE("Sponge", 19, false), + GLASS("Glass", 20, "12@8+263=8, 12@3+5@2=3, 12@12+369=12"), + LAPIS_ORE("Lapis Lazuli Ore", 21, false), + LAPIS_BLOCK("Lapis Lazuli Block", 22, "351:11@9"), + DISPENSER("Dispenser", 23, "4@7+331+261"), + SANDSTONE("Sandstone", 24, "12@4"), + CHISELED_SANDSTONE("Chiseled Sandstone", 24, (short) 1), + SMOOTH_SANDSTONE("Smooth Sandstone", 24, (short) 1, "24@4"), + NOTE_BLOCK("Note Block", 25, "5@8+331"), + BED_BLOCK("Bed Block", 26, false), + POWERED_RAIL("Powered Rail", 27, "266@6+331+280=6"), + DETECTOR_RAIL("Detector Rail", 28, "265@6+331+70=6"), + STICKY_PISTON_BASE("Sticky Piston", 29, "33+341"), + COBWB("Cobweb", 30, false), + DEAD_FERN("Dead Fern", 31, (short) 0), // non-natural dead shrub + TALL_GRASS("Tall Grass", 31, (short) 1), + FERN("Fern", 31, (short) 2, false), // or "Living Shrub" + DEAD_SHRUB("Dead Shrub", 32, false), + PISTON_BASE("Piston", 33, "5@3+4@4+265+331"), + PISTON_EXTENSION("Piston Extension", 34, false), // non-placable item + WOOL("White Wool", 35, (short) 0, "287@9"), + ORANGE_WOOL("Orange Wool", 35, (short) 1, "35+351:1"), + MAGENTA_WOOL("Magenta Wool", 35, (short) 2, "35+351:2"), + LIGHT_BLUE_WOOL("Light Blue Wool", 35, (short) 3, "35+351:3"), + YELLOW_WOOL("Yellow Wool", 35, (short) 4, "35+351:4"), + LIGHT_GREEN_WOOL("Light Green Wool", 35, (short) 5, "35+351:5"), + PINK_WOOL("Pink Wool", 35, (short) 6, "35+351:6"), + DARK_GRAY_WOOL("Dark Gray Wool", 35, (short) 7, "35+351:7"), + LIGHT_GRAY_WOOL("Light Gray Wool", 35, (short) 8, "35+351:8"), + CYAN_WOOL("Cyan Wool", 35, (short) 9, "35+351:9"), + PURPLE_WOOL("Purple Wool", 35, (short) 10, "35+351:10"), + BLUE_WOOL("Blue Wool", 35, (short) 11, "35+351:11"), + BROWN_WOOL("Brown Wool", 35, (short) 12, "35+351:12"), + DARK_GREEN_WOOL("Dark Green Wool", 35, (short) 13, "35+351:13"), + RED_WOOL("Red Wool", 35, (short) 14, "35+351:14"), + BLACK_WOOL("Black Wool", 35, (short) 15, "35+351:15"), + PISTON_MOVING_PIECE("Piston Moving Piece", 36, false), // non-placable item + YELLOW_FLOWER("Yellow Flower", 37), + RED_ROSE("Red Rose", 38), + BROWN_MUSHROOM("Brown Mushroom", 39), + RED_MUSHROOM("Red Mushroom", 40), + GOLD_BLOCK("Gold Block", 41, "266@9"), + IRON_BLOCK("Iron Block", 42, "265@9"), + DOUBLE_STEP("Double Slab", 43, (short) 0, "44@2", false), + SANDSTONE_DOUBLE_STEP("Sandstone Double Slab", 43, (short) 1, "44:1@2", false), + WOOD_DOUBLE_STEP("Wood Double Slab", 43, (short) 2, "44:2@2", false), + COBBLESTONE_DOUBLE_STEP("Cobblestone Double Slab", 43, (short) 3, "44:3@2", false), + BRICK_DOUBLE_STEP("Brick Double Slab", 43, (short) 4, "44:4@2", false), + STONE_BRICK_DOUBLE_STEP("Stone Brick Double Slab", 43, (short) 5, "44:5@2", false), + STEP("Slab", 44, (short) 0, "1@3=3"), + SANDSTONE_STEP("Sandstone Slab", 44, (short) 1, "24@3=3"), + WOOD_STEP("Wood Slab", 44, (short) 2, "5@3=3"), + COBBLESTONE_STEP("Cobblestone Slab", 44, (short) 3, "4@3=3"), + BRICK_STEP("Brick Slab", 44, (short) 4, "45@3=3"), + STONE_BRICK_STEP("Stone Brick Slab", 44, (short) 5, "98@3=3"), + BRICK("Brick Block", 45, "336@4"), + TNT("TNT", 46, "12@4+289@5"), + BOOKSHELF("Bookshelf", 47, "5@6+340@3"), + MOSSY_COBBLESTONE("Moss Stone", 48), + OBSIDIAN("Obsidian", 49), + TORCH("Torch", 50, "280+263=4"), + FIRE("Fire", 51, false), + //ETERNAL_FIRE("EternalFire", 51, 16, false), + MOB_SPAWNER("Monster Spawner", 52, false), + WOOD_STAIRS("Wooden Stairs", 53, "5@6=4"), + CHEST("Chest", 54, "5@8"), + REDSTONE_WIRE("Redstone Wire", 55, false), + DIAMOND_ORE("Diamond Ore", 56, false), + DIAMOND_BLOCK("Diamond Block", 57, "264@9"), + WORKBENCH("Crafting Table", 58, "5@4"), + CROPS("Crops", 59, false), + SOIL("Soil", 60, false), + FURNACE("Furnace", 61, "4@8"), + BURNING_FURNACE("Burning Furnace", 62, false), + SIGN_POST("Sign Post", 63, false), + WOODEN_DOOR("Wooden Door Block", 64, false), + LADDER("Ladder", 65, "280@7=2"), + RAILS("Rails", 66, "265@6+280=16"), + COBBLESTONE_STAIRS("Cobblestone Stairs", 67, "4@6"), + WALL_SIGN("Wall Sign", 68, false), + LEVER("Lever", 69, "4+280"), + STONE_PLATE("Stone Pressure Plate", 70, "1@2"), + IRON_DOOR_BLOCK("Iron Door Block", 71, false), + WOOD_PLATE("Wooden Pressure Plate", 72, "5@2"), + REDSTONE_ORE("Redstone Ore", 73, false), + GLOWING_REDSTONE_ORE("Glowing Redstone Ore", 74, false), + REDSTONE_TORCH_OFF("Redstone Torch Off", 75, false), + REDSTONE_TORCH("Redstone Torch", 76, "280+331"), + STONE_BUTTON("Stone Button", 77, "1@2"), + SNOW("Snow Covering", 78, false), + ICE("Ice", 79, false), + SNOW_BLOCK("Snow Block", 80), + CACTUS("Cactus", 81), + CLAY("Clay Block", 82, "337@4"), + SUGAR_CANE_BLOCK("Sugar Cane Block", 83, false), + JUKEBOX("Jukebox", 84, "5@8+264"), + FENCE("Fence", 85, "280@6"), + PUMPKIN("Pumpkin", 86), + NETHERRACK("Netherrack", 87), + SOUL_SAND("Soulsand", 88), + GLOWSTONE("Glowstone", 89, "348@9"), + PORTAL("Portal", 90, false), + JACK_O_LANTERN("Jack O'Lantern", 91), + CAKE_BLOCK("Cake Block", 92, false), + DIODE_BLOCK_OFF("Repeater Block Off", 93, false), + DIODE_BLOCK_ON("Repeater Block On", 94, false), + LOCKED_CHEST("Locked Chest", 95, false), + TRAPDOOR("Trapdoor", 96, "5@6=2"), + SILVERFISH_STONE("Silverfish Stone", 97, false), + SILVERFISH_COBBLESTONE("Silverfish Cobblestone", 97, (short) 1, false), + SILVERFISH_STONEBRICK("Silverfish Stone Brick", 97, (short) 2, false), + STONE_BRICK("Stone Brick", 98, (short) 0, "1@4=4"), + MOSSY_STONE_BRICK("Mossy Stone Brick", 98, (short) 1), + CRACKED_STONE_BRICK("Cracked Stone Brick", 98, (short) 2), + CHISELED_STONE_BRICK("Chiseled Stone Brick", 98, (short) 3), + BROWN_MUSHROOM_BLOCK("Brown Mushroom Block", 99, false), + RED_MUSHROOM_BLOCK("Red Mushroom Block", 100, false), + IRON_BARS("Iron Bars", 101, "265@6=16"), + GLASS_PANE("Glass Pane", 102, "20@6=16"), + MELON("Melon", 103, "360@9"), + PUMPKIN_STEM("Pumpkin Stem Block", 104, false), + MELON_STEM("Melon Stem Block", 105, false), + VINE("Vine", 106), + FENCE_GATE("Fence Gate", 107, "4@280+5@2"), + BRICK_STAIRS("Brick Stairs", 108, "45@6=4"), + STONE_STAIRS("Stone Stairs", 109, "98@6=4"), + MYCELIUM("Mycelium", 110, false), + LILLY_PAD("Lilly Pad", 111), + NETHER_BRICK_BLOCK("Nether Brick Block", 112), + NETHER_BRICK_FENCE("Nether Brick Fence", 113, "112@6=6"), + NETHER_BRICK_STAIRS("Nether Brick Stairs", 114, "112@6=6"), + NETHER_WART_Block("Nether Wart Block", 115, false), + ENCHANTMENT_TABLE("Enchantment Table", 116, "49@4+264@2+340"), + BREWING_STAND_BLOCK("Brewing Stand", 117, false), + CAULDRON_BLOCK("Cauldron", 118, false), + END_PORTAL("End Portal", 119, false), + END_PORTAL_FRAME("End Portal Frame", 120, false), + END_STONE("End Stone", 121, false), + DRAGON_EGG("Dragon Egg", 122, false), + REDSTONE_LAMP_ON("Redstone Lamp ON", 123, false), + REDSTONE_LAMP_OFF("Redstone Lamp", 124, "331@4+89"), + // legacy support.. not bothering with crafting recipies for now.. + WOODEN_DOUBLE_SLAB("Wooden Double Slab", 125), + WOODEN_SLAB("Wooden Slab", 126), + COCOA_POD("Cocoa Pod", 127, false), + SANDSTONE_STAIRS("Sandstone Stairs", 128), + EMERALD_ORE("Emerald Ore", 129, false), + ENDER_CHEST("Ender Chest", 130), + TRIPWIRE_HOOK("Tripwire Hook", 131), + // 132: tripwire + EMERALD_BLOCK("Emerald Block", 133), + SPRUCE_WOODEN_STAIRS("Spruce Wood Stairs", 134), + BIRCH_WOOD_STAIRS("Birch Wood Stairs", 135), + JUNGLE_WOOD_STAIRS("Jungle Wood Stairs", 136), + COMMAND_BLOCK("Command Block", 137, false), + BEACON_BLOCK("Beacon", 138), + COBBLESTONE_WALL("Cobblestone Wall", 139), + FLOWER_POT_BLOCK("Flower Pot", 140, false), + WOODEN_BUTTON("Wooden Button", 143), + SKELETON_HEAD_BLOCK("Skeleton Head Block", 144, false), + WITHER_SKELETON_HEAD_BLOCK("Wither Skeleton Head Block", 144, (short) 1, false), + ZOMBIE_HEAD_BLOCK("Zombie Head Block", 144, (short) 2, false), + HUMAN_HEAD_BLOCK("Human Head Block", 144, (short) 3, false), + CREEPER_HEAD_BLOCK("Creeper Head Block", 144, (short) 4, false), + ANVIL("Anvil", 145), + // 1.5.1 - 2 + TRAPPED_CHEST("Trapped Chest", 146), + GOLD_PRESSURE_PLATE("Gold Pressure Plate", 147), + IRON_PRESSURE_PLATE("Iron Pressure Plate", 148), + DAYLIGHT_SENSOR("Daylight Sensor", 151), + REDSTONE_BLOCK("Redstone Block", 152), + NETHER_QUARTZ_ORE("Nether Quartz Ore", 153), + HOPPER("Hopper", 154), + QUARTZ_BLOCK("Quartz Block", 155), + QUARTZ_STAIRS("Quartz Stairs", 156), + ACTIVATOR_RAIL("Activator Rail", 157), + DROPPER("Dropper", 158), + // 1.6 + //colored clay + WHITE_STAINED_CLAY("White Stained Clay", 159, (short) 0), + ORANGE_STAINED_CLAY("Orange Stained Clay", 159, (short) 1), + MAGENTA_STAINED_CLAY("Magenta Stained Clay", 159, (short) 2), + LIGHT_BLUE_STAINED_CLAY("Light Blue Stained Clay", 159, (short) 3), + YELLOW_STAINED_CLAY("Yellow Stained Clay", 159, (short) 4), + LIGHT_GREEN_STAINED_CLAY("Light Green Stained Clay", 159, (short) 5), + PINK_STAINED_CLAY("Pink Stained Clay", 159, (short) 6), + DARK_GRAY_STAINED_CLAY("Dark Gray Stained Clay", 159, (short) 7), + LIGHT_GRAY_STAINED_CLAY("Light Gray Stained Clay", 159, (short) 8), + CYAN_STAINED_CLAY("Cyan Stained Clay", 159, (short) 9), + PURPLE_STAINED_CLAY("Purple Stained Clay", 159, (short) 10), + BLUE_STAINED_CLAY("Blue Stained Clay", 159, (short) 11), + BROWN_STAINED_CLAY("Brown Stained Clay", 159, (short) 12), + DARK_GREEN_STAINED_CLAY("Dark Green Stained Clay", 159, (short) 13), + RED_STAINED_CLAY("Red Stained Clay", 159, (short) 14), + BLACK_STAINED_CLAY("Black Stained Clay", 159, (short) 15), + //colored stained glass + WHITE_STAINED_GLASS("White Stained Glass", 160, (short) 0), + ORANGE_STAINED_GLASS("Orange Stained Glass", 160, (short) 1), + MAGENTA_STAINED_GLASS("Magenta Stained Glass", 160, (short) 2), + LIGHT_BLUE_STAINED_GLASS("Light Blue Stained Glass", 160, (short) 3), + YELLOW_STAINED_GLASS("Yellow Stained Glass", 160, (short) 4), + LIGHT_GREEN_STAINED_GLASS("Light Green Stained Glass", 160, (short) 5), + PINK_STAINED_GLASS("Pink Stained Glass", 160, (short) 6), + DARK_GRAY_STAINED_GLASS("Dark Gray Stained Glass", 160, (short) 7), + LIGHT_GRAY_STAINED_GLASS("Light Gray Stained Glass", 160, (short) 8), + CYAN_STAINED_GLASS("Cyan Stained Glass", 160, (short) 9), + PURPLE_STAINED_GLASS("Purple Stained Glass", 160, (short) 10), + BLUE_STAINED_GLASS("Blue Stained Glass", 160, (short) 11), + BROWN_STAINED_GLASS("Brown Stained Glass", 160, (short) 12), + DARK_GREEN_STAINED_GLASS("Dark Green Stained Glass", 160, (short) 13), + RED_STAINED_GLASS("Red Stained Glass", 160, (short) 14), + BLACK_STAINED_GLASS("Black Stained Glass", 160, (short) 15), + + ACACIA_LEAVES("Acacia Leaves", 161, (byte) 0), + DARK_OAK_LEAVES("Dark Oak Leaves", 161, (byte) 1), + ACACIA_LOG("Acacia Leaves", 161, (byte) 0), + DARK_OAK_LOG("Dark Oak Leaves", 161, (byte) 1), + ACACIA_WOOD_STAIRS("Acacia Wood Stairs", 163), + DARK_OAK_WOOD_STAIRS("Dark Oak Wood Stairs", 164), + //SLIME_BLOCK("Slime Block", 165), + //MINECRAFT_BARRIER("Barrier", 166), + //IRON_TRAPDOOR("Iron Trapdoor", 167), + HAY_BLOCK("Hay Block", 170), + WHITE_CARPET("White Carpet", 171, (short) 0), + ORANGE_CARPET("Orange Carpet", 171, (short) 1), + MAGENTA_CARPET("Magenta Carpet", 171, (short) 2), + LIGHT_BLUE_CARPET("Light Blue Carpet", 171, (short) 3), + YELLOW_CARPET("Yellow Carpet", 171, (short) 4), + LIGHT_GREEN_CARPET("Light Green Carpet", 171, (short) 5), + PINK_CARPET("Pink Carpet", 171, (short) 6), + DARK_GRAY_CARPET("Dark Gray Carpet", 171, (short) 7), + LIGHT_GRAY_CARPET("Light Gray Carpet", 171, (short) 8), + CYAN_CARPET("Cyan Carpet", 171, (short) 9), + PURPLE_CARPET("Purple Carpet", 171, (short) 10), + BLUE_CARPET("Blue Carpet", 171, (short) 11), + BROWN_CARPET("Brown Carpet", 171, (short) 12), + DARK_GREEN_CARPET("Dark Green Carpet", 171, (short) 13), + RED_CARPET("Red Carpet", 171, (short) 14), + BLACK_CARPET("Black Carpet", 171, (short) 15), + // + HARDENED_CLAY("Hardened Clay", 172), + COAL_BLOCK("Coal Block", 173), + PACKED_ICE("Packed Ice", 174), + SUNFLOWER("Sunflower", 175, (byte) 0), + LILAC("Lilac", 175, (byte) 1), + DOUBLE_TALL_GRASS("Double Tall Grass", 175, (byte) 2), + LARGE_FERN("Large Fern", 175, (byte) 3), + ROSE_BUSH("Rose Bush", 175, (byte) 4), + PEONY("Peony", 175, (byte) 5), + // + // Items + IRON_SPADE("Iron Shovel", 256, "280@2+265", (short) 251), + IRON_PICKAXE("Iron Pickaxe", 257, "280@2+265@3", (short) 251), + IRON_AXE("Iron Axe", 258, "280@2+265@3", (short) 251), + FLINT_AND_STEEL("Flint And Steel", 259, "318+265", (short) 65), + APPLE("Apple", 260), + BOW("Bow", 261, "280@3+287@3", 1, (short) 385), + ARROW("Arrow", 262, "280+318+288"), + COAL("Coal", 263, (short) 0), + CHARCOAL("Charcoal", 263, (short) 1), + DIAMOND("Diamond", 264), + IRON_INGOT("Iron Ingot", 265, "15@8+263=8, 15@3+5@2=3, 15@12+369=12"), + GOLD_INGOT("Gold Ingot", 266, "14@8+263=8, 14@3+5@2=3, 14@12+369=12, 371@9"), + IRON_SWORD("Iron Sword", 267, "280+265@2", (short) 251), + WOOD_SWORD("Wooden Sword", 268, "280+5@2", (short) 60), + WOOD_SPADE("Wooden Shovel", 269, "280@2+5", (short) 60), + WOOD_PICKAXE("Wooden Pickaxe", 270, "280@2+5@3", (short) 60), + WOOD_AXE("Wooden Axe", 271, "280@2+5@3", (short) 60), + STONE_SWORD("Stone Sword", 272, "280+5@2", (short) 132), + STONE_SPADE("Stone Shovel", 273, "280@2+4", (short) 132), + STONE_PICKAXE("Stone Pickaxe", 274, "280@2+4@3", (short) 132), + STONE_AXE("Stone Axe", 275, "280@2+4@3", (short) 132), + DIAMOND_SWORD("Diamond Sword", 276, "280+264@2", (short) 1562), + DIAMOND_SPADE("Diamond Shovel", 277, "280@2+264", (short) 1562), + DIAMOND_PICKAXE("Diamond Pickaxe", 278, "280@2+264@3", (short) 1562), + DIAMOND_AXE("Diamond Axe", 279, "280@2+264@3", (short) 1562), + STICK("Stick", 280, "5@2=4"), + BOWL("Bowl", 281, "5@3=4"), + MUSHROOM_SOUP("Mushroom Soup", 282, "281+39+40", 1), + GOLD_SWORD("Gold Sword", 283, "280+266@2", (short) 33), + GOLD_SPADE("Gold Shovel", 284, "280@2+266", (short) 33), + GOLD_PICKAXE("Gold Pickaxe", 285, "280@2+266@3", (short) 33), + GOLD_AXE("Gold Axe", 286, "280@2+266@3", (short) 33), + STRING("String", 287), + FEATHER("Feather", 288), + SULPHUR("Gunpowder", 289), + WOOD_HOE("WoodenHoe", 290, "280@2+5@2", (short) 60), + STONE_HOE("StoneHoe", 291, (short) 132), + IRON_HOE("IronHoe", 292, (short) 251), + DIAMOND_HOE("Diamond Hoe", 293, (short) 1562), + GOLD_HOE("Gold Hoe", 294, (short) 33), + SEEDS("Seeds", 295), + WHEAT("Wheat", 296), + BREAD("Bread", 297, "296@3"), + LEATHER_HELMET("Leather Helmet", 298, "334@5", (short) 34), + LEATHER_CHESTPLATE("Leather Chestplate", 299, "334@8", (short) 49), + LEATHER_LEGGINGS("Leather Leggings", 300, "334@7", (short) 46), + LEATHER_BOOTS("Leather Boots", 301, "334@4", (short) 40), + // chainmail is currently illegal, but should be added later.. + CHAINMAIL_HELMET("Chainmail Helmet", 302, (short) 67, false), + CHAINMAIL_CHESTPLATE("Chainmail Chestplate", 303, (short) 96, false), + CHAINMAIL_LEGGINGS("Chainmail Leggings", 304, (short) 92, false), + CHAINMAIL_BOOTS("Chainmail Boots", 305, (short) 79, false), + IRON_HELMET("Iron Helmet", 306, "265@5", (short) 136), + IRON_CHESTPLATE("Iron Chestplate", 307, "265@8", (short) 192), + IRON_LEGGINGS("Iron Leggings", 308, "265@7", (short) 184), + IRON_BOOTS("Iron Boots", 309, "265@4", (short) 160), + DIAMOND_HELMET("Diamond Helmet", 310, "265@5", (short) 272), + DIAMOND_CHESTPLATE("Diamond Chestplate", 311, "265@8", (short) 285), + DIAMOND_LEGGINGS("Diamond Leggings", 312, "265@7", (short) 368), + DIAMOND_BOOTS("Diamond Boots", 313, "265@4", (short) 320), + GOLD_HELMET("Gold Helmet", 314, "265@5", (short) 68), + GOLD_CHESTPLATE("Gold Chestplate", 315, "265@8", (short) 96), + GOLD_LEGGINGS("Gold Leggings", 316, "265@7", (short) 92), + GOLD_BOOTS("Gold Boots", 317, "265@4", (short) 80), + FLINT("Flint", 318), + PORK("Raw Porkchop", 319), + GRILLED_PORK("Cooked Porkchop", 320, "319@8+263=8, 319@3+5@2=3, 319@12+369=12"), + PAINTING("Painting", 321, "280@8+15"), + GOLDEN_APPLE("Golden Apple", 322, "260+371@8"), + ENCHANTED_GOLDEN_APPLE("Enchanted Golden Apple", 322, (short) 1, "260+41@8"), + SIGN("Sign", 323, "280+5@6", 1), + WOOD_DOOR("Wooden Door", 324, "5@6", 1), + BUCKET("Bucket", 325, "265@3", 1), + WATER_BUCKET("Water Bucket", 326, 1), + LAVA_BUCKET("Lava Bucket", 327, 1), + MINECART("Minecart", 328, "265@5", 1), + SADDLE("Saddle", 329, 1), + IRON_DOOR("Iron Door", 330, "265@6", 1), + REDSTONE("Redstone", 331), + SNOW_BALL("Snowball", 332, 16), + BOAT("Boat", 333, "5@5", 1), + LEATHER("Leather", 334), + MILK_BUCKET("Milk Bucket", 335, 1), + CLAY_BRICK("Clay Brick", 336, "337@8+263=8, 337@3+5@2=3, 337@12+369=12"), + CLAY_BALL("Clay Balls", 337), + SUGAR_CANE("Sugar Cane", 338), + PAPER("Paper", 339, "338@3=3"), + BOOK("Book", 340, "339@3"), + SLIME_BALL("Slimeball", 341), + STORAGE_MINECART("Storage Minecart", 342, "328+54", 1), + POWERED_MINECART("Powered Minecart", 343, "328+61", 1), + EGG("Egg", 344, 16), + COMPASS("Compass", 345, "265@4+331"), + FISHING_ROD("Fishing Rod", 346, "280@3+287@2", (short) 65), // was 33, but 1.0.0 ItemFishingRod.java says 64. + WATCH("Clock", 347, "266@4+331"), + GLOWSTONE_DUST("Glowstone Dust", 348), + RAW_FISH("Raw Fish", 349), + COOKED_FISH("Cooked Fish", 350, "349@8+263=8, 349@3+5@2=3, 349@12+369=12"), + INK_SACK("Ink Sac", 351, (short) 0), + RED_DYE("Red Dye", 351, (short) 1, "38=2"), + GREEN_DYE("Green Dye", 351, (short) 2, "81@8+263=8, 81@3+5@2=3, 81@12+369=12"), + BROWN_DYE("Cocoa Bean", 351, (short) 3), // no current method for crafting cocoa + BLUE_DYE("Lapis Lazuli", 351, (short) 4, "22=9"), + PURPLE_DYE("Purple Dye", 351, (short) 5, "351:4+351:1=2"), + CYAN_DYE("Cyan Dye", 351, (short) 6, "351:4+351:2=2"), + LIGHT_GRAY_DYE("Light Gray Dye", 351, (short) 7, "351:8+351:15=2"), + GRAY_DYE("Gray Dye", 351, (short) 8, "351:15+351=2"), + PINK_DYE("Pink Dye", 351, (short) 9, "351:1+351=2"), + LIME_DYE("Lime Dye", 351, (short) 10, "351:2+351:15=2"), + YELLOW_DYE("Yellow Dye", 351, (short) 11, "37=2"), + LIGHT_BLUE_DYE("Light Blue Dye", 351, (short) 12, "351:4+351:15=2"), + MAGENTA_DYE("Magenta Dye", 351, (short) 13, "351:4+351:9=2"), + ORANGE_DYE("Orange Dye", 351, (short) 14, "351:1+351:11=2"), + BONEMEAL("Bone Meal", 351, (short) 15, "352=2"), + BONE("Bone", 352), + SUGAR("Sugar", 353, "338"), + CAKE("Cake", 354, "335@3+353@2+344+296@3", 1), + BED("Bed", 355, "5@3+35@3", 1), + DIODE("Redstone Repeater", 356, "1@3+76@2+331"), + COOKIE("Cookie", 357, "296@2+351:3"), + MAP("Map", 358, "339@8+345"), + SHEARS("Shears", 359, "265@2", (short) 239), + MELON_SLICE("Melon Slice", 360), + PUMPKIN_SEEDS("Pumpkin Seeds", 361, "360"), + MELON_SEEDS("Melon Seeds", 362, "360"), + RAW_BEEF("Raw Beef", 363), + STEAK("Steak", 364, "363@8+263=8, 363@3+5@2=3, 363@12+369=12"), + RAW_CHICKEN("Raw Chicken", 365), + COOKED_CHICKEN("Cooked Chicken", 366, "365@8+263=8, 365@3+5@2=3, 365@12+369=12"), + ROTTEN_FLESH("Rotten Flesh", 367), + ENDER_PEARL("Ender Pearl", 368), + BLAZE_ROD("Blaze Rod", 369), + GHAST_TEAR("Ghast Tear", 370), + GOLD_NUGGET("Gold Nugget", 371), + NETHER_WART("Nether Wart", 372), + + POTION("Potion", 373), // will need to add values later, so far unknown + POTION_REGENERATION("Regeneration Potion", 373, (short) 1), // Potion of Regeneration (0:45) + POTION_MOVESPEED("Speed Potion", 373, (short) 2), // Potion of Swiftness (3:00) + POTION_FIRERESISTANCE("Fire Resistance Potion", 373, (short) 3), // Potion of Fire Resistance (3:00) + POTION_POISON("Poison Potion", 373, (short) 4), // Potion of Poison (0:45) + POTION_HEAL("Health Potion", 373, (short) 5), // Potion of Healing (Instant Health) + POTION_CLEAR("Potion of Night Vision", 373, (short) 6), + POTION_CLEAR_2("Clear Potion", 373, (short) 7), // Clear Potion (no effects) + POTION_WEAKNESS("Weakness Potion", 373, (short) 8), // Potion of Weakness (1:30) + POTION_DAMEGEBOOST("Strength Potion", 373, (short) 9), // Potion of Strength (3:00) + POTION_MOVESLOWDOWN("Slowness Potion", 373, (short) 10), // Potion of Slowness (1:30) + POTION_DIFFUSE("Diffuse Potion", 373, (short) 11), // Diffuse Potion (no effects) + POTION_HARM("Harm Potion", 373, (short) 12), // Potion of Harming (Instant Damage) + POTION_ARTLESS("Artless Potion", 373, (short) 13), // Artless Potion (no effects) + POTION_THIN("Potion of Invisibility", 373, (short) 14), + POTION_THIN_2("Thin Potion", 373, (short) 15), // Thin Potion (no effects) + POTION_AWKWARD("Awkward Potion", 373, (short) 16), // Awkward Potion (no effects) + POTION_REGENERATION_2("Regeneration Potion", 373, (short) 17), // Potion of Regeneration (0:45) + POTION_MOVESPEED_2("Speed Potion", 373, (short) 18), // Potion of Swiftness (3:00) + POTION_FIRERESISTANCE_2("Fire Resistance Potion", 373, (short) 19), // Potion of Fire Resistance (3:00) + POTION_POISON_2("Poison Potion", 373, (short) 20), // Potion of Poison (0:45) + + POTION_DIGSPEED("Haste Potion", 373, (short) 3), + POTION_DIGSLOWDOWN("Fatigue Potion", 373, (short) 4), + POTION_JUMP("Jump Potion", 373, (short) 8), + POTION_CONFUSION("Nausea Potion", 373, (short) 9), + POTION_RESISTANCE("Resistance Potion", 373, (short) 11), + POTION_WATERBREATHING("Water Breathing Potion", 373, (short) 13), + POTION_INVISIBILITY("Invisibility Potion", 373, (short) 14), + POTION_BLINDNESS("Blindness Potion", 373, (short) 15), + POTION_NIGHTVISION("Night Vision Potion", 373, (short) 16), + POTION_HUNGER("Hunger Potion", 373, (short) 17), + + GLASS_BOTTLE("Glass Bottle", 374, "20@3"), + SPIDER_EYE("Spider Eye", 375), + FERMENTED_SPIDER_EYE("Fermented Spider Eye", 376, "375+39+352"), + BLAZE_POWDER("Blaze Powder", 377, "369=2"), + MAGMA_CREAM("Magma Cream", 378, "377+341"), + BREWING_STAND("Brewing Stand", 379, "4@3+369"), + CAULDRON("Cauldron", 380, "265@7"), + EYE_OF_ENDER("Eye of Ender", 381, "368+377"), + GLISTERING_MELON("Glistering Melon", 382, "360+371"), + + SPAWN_EGG_CREEPER("Creeper Egg", 383, (short) 50, false), + SPAWN_EGG_SKELETON("Skeleton Egg", 383, (short) 51, false), + SPAWN_EGG_SPIDER("Spider Egg", 383, (short) 52, false), + SPAWN_EGG_GIANT("Giant Egg", 383, (short) 53, false), + SPAWN_EGG_ZOMBIE("Zombie Egg", 383, (short) 54, false), + SPAWN_EGG_SLIME("Slime Egg", 383, (short) 55, false), + SPAWN_EGG_GHAST("Ghast Egg", 383, (short) 56, false), + SPAWN_EGG_PIGZOMBIE("PigZombie Egg", 383, (short) 57, false), + SPAWN_EGG_ENDERMAN("Enderman Egg", 383, (short) 58, false), + SPAWN_EGG_CAVESPIDER("CaveSpider Egg", 383, (short) 59, false), + SPAWN_EGG_SILVERFISH("Silverfish Egg", 383, (short) 60, false), + SPAWN_EGG_BLAZE("Blaze Egg", 383, (short) 61, false), + SPAWN_EGG_LAVASLIME("LavaSlime Egg", 383, (short) 62, false), + SPAWN_EGG_ENDERDRAGON("EnderDragon Egg", 383, (short) 63, false), + SPAWN_EGG_PIG("Pig Egg", 383, (short) 90, false), + SPAWN_EGG_SHEEP("Sheep Egg", 383, (short) 91, false), + SPAWN_EGG_COW("Cow Egg", 383, (short) 92, false), + SPAWN_EGG_CHICKEN("Chicken Egg", 383, (short) 93, false), + SPAWN_EGG_WOLF("Wolf Egg", 383, (short) 94, false), + SPAWN_EGG_MUSHROOMCOW("MushroomCow Egg", 383, (short) 95, false), + SPAWN_EGG_SNOWMAN("SnowMan Egg", 383, (short) 96, false), + SPAWN_EGG_VILLAGER("Villager Egg", 383, (short) 97, false), + SPAWN_EGG_OCELOT("Ocelot Egg", 383, (short) 98, false), + SPAWN_EGG_IRON_GOLEM("Iron Golem Egg", 383, (short) 99, false), // will be added later (not yet implemented in mc) + + EXP_BOTTLE("Bottle o' Enchanting", 384, false), // (Experience bottle) + FIREBALL("Fire Charge", 385, "377+289+263=3, 377+289+263:1=3"), + // legacy support: not bothering with crafting recipies for now + BOOK_AND_QUILL("Book and Quill", 386), + WRITTEN_BOOK("Written Book", 387), + EMERALD("Emerald", 388), + ITEM_FRAME("Item Frame", 389), + FLOWER_POT("Flower Pot", 390), + CARROT("Carrot", 391), + POTATO("Potato", 392), + BAKED_POTATO("Baked Potato", 393), + POISONOUS_POTATO("Poisonous Potato", 394), + EMPTY_MAP("Empty Map", 395), + GOLDEN_CARROT("Golden Carrot", 396), + SKELETON_HEAD("Skeleton Head", 397), + WITHER_SKELETON_HEAD("Wither Skeleton Head", 397, (short) 1), + ZOMBIE_HEAD("Zombie Head", 397, (short) 2), + HUMAN_HEAD("Human Head", 397, (short) 3), + CREEPER_HEAD("Creeper Head", 397, (short) 4), + CARROT_ON_A_STICK("Carrot on a Stick", 398, (short) 26), + NETHER_STAR("Nether Star", 399), + PUMPKIN_PIE("Pumpkin Pie", 400), + // 1.4.6 + FIREWORK_ROCKET("Firework Rocket", 401), + FIREWORK_STAR("Firework Star", 402), + ENCHANTED_BOOK("Enchanted Book", 403), + // 1.5.1 - 2 + REDSTONE_COMPARATOR("Redstone Comparator", 404), + NETHER_BRICK("Nether Brick", 405), + NETHER_QUARTZ("Nether Quartz", 406), + TNT_MINECART("TNT Minecart", 407), + HOPPER_MINECART("Hopper Minecart", 408), + // 1.6 + IRON_HORSE_ARMOR("Iron Horse Armor", 417), + GOLD_HORSE_ARMOR("Gold Horse Armor", 418), + DIAMOND_HORSE_ARMOR("Diamond Horse Armor", 419), + LEAD("Lead", 420), + NAME_TAG("Name Tag", 421), + // 1.7 + COMMAND_BLOCK_MINECART("Command Block Minecart", 422), + // Records + GOLD_RECORD("13 Disk", 2256, 1), // Gold Record + GREEN_RECORD("Cat Disc", 2257, 1), // Green Record + ORANGE_RECORD("Blocks Disc", 2258, 1), + RED_RECORD("Chirp Disc", 2259, 1), + LIME_RECORD("Far Disc", 2260, 1), + BLUE_RECORD("Mall Disc", 2261, 1), + PURPLE_RECORD("Mellohi Disc", 2262, 1), + BLACK_RECORD("Stal Disc", 2263, 1), + WHITE_RECORD("Strad Disc", 2264, 1), + GREEN_LIME_RECORD("Ward Disc", 2265, 1), // has half green, half lime + BROKEN_RECORD("11 Disc", 2266, 1), + // 1.6? + CYAN_RECORD("Wait Disk", 2267, 1); +// Item Information + private int itemId; + private short itemData; + private boolean isLegal = true, // is is legitemitely obtainable + hasData = false; // if data defines this item + private int maxStack = 64; + private short maxdamage = 0; // indicates that this is a tool + public String name = null;//, category = null; + private LinkedList recipes = new LinkedList(); + // what item ids' data resemble max damage value + // (must be in ascending order) + private static int[] tools = new int[]{ + 256, 257, 258, 259, 261, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 276, 277, 278, 278, 279, + 283, 284, 285, 286, 290, 291, 292, 293, 294, + 298, 299, 300, 301, 302, 303, 304, 305, + 306, 307, 308, 309, 310, 311, 312, 313, + 314, 315, 316, 317, 346, 359, 398}; + // items (other than tools) that aren't good to stack + private static int[] unsafeStack = new int[]{ + 282, 325, 326, 327, 335}; + + private JItems(String name, int id) { + if (SetID(id)) { + this.name = name; + } + } + + private JItems(String name, int id, boolean legal) { + if (SetID(id)) { + this.name = name; + isLegal = legal; + } + } + + private JItems(String name, int id, short dat) { + if (SetID(id)) { + this.name = name; + itemData = dat; + hasData = true; + } + } + + private JItems(String name, int id, int maxStack) { + if (SetID(id)) { + this.name = name; + this.maxStack = maxStack; + } + } + + private JItems(String name, int id, int maxStack, boolean legal) { + if (SetID(id)) { + this.name = name; + this.maxStack = maxStack; + isLegal = legal; + } + } + + private JItems(String name, int id, short dat, boolean legal) { + if (SetID(id)) { + this.name = name; + itemData = dat; + isLegal = legal; + hasData = true; + } + } + + private JItems(String name, int id, String craftRecipies) { + if (SetID(id)) { + this.name = name; + _setRecipe(craftRecipies); + } + } + + private JItems(String name, int id, String craftRecipies, boolean legal) { + if (SetID(id)) { + this.name = name; + isLegal = legal; + _setRecipe(craftRecipies); + } + } + + private JItems(String name, int id, short dat, String craftRecipies) { + if (SetID(id)) { + this.name = name; + itemData = dat; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(String name, int id, String craftRecipies, int maxStack) { + if (SetID(id)) { + this.name = name; + this.maxStack = maxStack; + _setRecipe(craftRecipies); + } + } + + private JItems(String name, int id, String craftRecipies, int maxStack, boolean legal) { + if (SetID(id)) { + this.name = name; + isLegal = legal; + this.maxStack = maxStack; + _setRecipe(craftRecipies); + } + } + + private JItems(String name, int id, String craftRecipies, short maxDamage) { + if (SetID(id)) { + this.name = name; + this.maxdamage = maxDamage; + this.maxStack = maxdamage > 0 ? 1 : 64; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(String name, int id, String craftRecipies, short maxDamage, boolean legal) { + if (SetID(id)) { + this.name = name; + isLegal = legal; + this.maxdamage = maxDamage; + this.maxStack = maxdamage > 0 ? 1 : 64; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(String name, int id, String craftRecipies, int maxStack, short maxDamage) { + if (SetID(id)) { + this.name = name; + this.maxStack = maxStack; + this.maxdamage = maxDamage; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(String name, int id, short dat, String craftRecipies, int maxStack, short maxDamage) { + if (SetID(id)) { + this.name = name; + itemData = dat; + this.maxStack = maxStack; + this.maxdamage = maxDamage; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(String name, int id, short dat, String craftRecipies, boolean legal) { + if (SetID(id)) { + this.name = name; + itemData = dat; + isLegal = legal; + _setRecipe(craftRecipies); + hasData = true; + } + } + + private JItems(int id) { + if (SetID(id)) { + AutoName(); + } + } + + private JItems(int id, short dat) { + if (SetID(id)) { + itemData = dat; + AutoName(); + hasData = true; + } + } + +// private JItems(ItemStack i) { +// itemId = i.getTypeId(); +// //if(maxdamage==0)// i.getDurability() 0; + } + + public boolean IsLegal() { + return isLegal; + } + + public int MaxStackSize() { + return maxStack; + } + + public String IdDatStr() { + return String.format("%d:%d", itemId, itemData); + } + + public String getName() { + return name == null ? this.name() : name; + } + + public void setName(String newname) { + if (newname != null) { + this.name = newname; + } + } + + protected void setMaxStack(int stack) { + maxStack = stack; + } + + private JItem _getIdDat(String datStr) { + String s1 = datStr.contains(":") ? datStr.substring(0, datStr.indexOf(':')).trim() : datStr, + s2 = datStr.contains(":") ? datStr.substring(datStr.indexOf(':') + 1).trim() : ""; + JItem r = new JItem(); + r.itemId = CheckInput.GetInt(s1, -1); + r.itemDat = CheckInput.GetShort(s2, (short) 0); + if (r.itemId > 0) { + return r; + } + return null; + } + + private CraftRecipe _getRecipe(String craftStr) { + CraftRecipe ret = new CraftRecipe(); + + // get result amount + if (craftStr.contains("=")) { + if (craftStr.split("=").length > 2 || craftStr.length() == craftStr.indexOf('=')) { + return null; + } + ret.resultAmount = CheckInput.GetInt(craftStr.substring(craftStr.indexOf('=') + 1), 0); + craftStr = craftStr.substring(0, craftStr.indexOf('=')); + } else { + ret.resultAmount = 1; + } + // extract all items + for (String i : craftStr.split("\\+")) { + if (i.contains("@")) { + if (i.length() > i.indexOf('@')) { + ret.AddItem(_getIdDat(i.substring(0, i.indexOf('@'))), + CheckInput.GetInt(i.substring(i.indexOf('@') + 1), 0)); + } else { + ret.AddItem(_getIdDat(i.substring(0, i.indexOf('@')))); + } + } else { + ret.AddItem(_getIdDat(i)); + } + } + return ret.totalItems() == 0 ? null : ret; + } + + private void _setRecipe(String craft) { + if (craft != null) { + for (String i : craft.split(",")) { + CraftRecipe toAdd = _getRecipe(i); + if (toAdd != null) { + recipes.add(toAdd); + } + } + } + } + + public boolean HasRecipe() { + return recipes.size() > 0; + } + + public Kit[] GetRecipeAsKit(int index) { + if (index >= 0 && index < recipes.size()) { + return recipes.get(index).getKits(); + } + return null; + } + + public Kit[] GetFullRecipeKits() { + LinkedList ret = new LinkedList(); + for (CraftRecipe c : recipes) { + ret.addAll(Arrays.asList(c.getKits())); + } + return ret.toArray(new Kit[0]); + } + + public CraftRecipe GetRecipe(int index) { + if (index >= 0 && index < recipes.size()) { + return recipes.get(index); + } + return null; + } + + public CraftRecipe[] GetRecipes() { + return recipes.toArray(new CraftRecipe[0]); + } + + public ItemStack toItemStack() { + //return isEntity() || itemId < 0 ? null : new ItemStack(itemId, 1, (short) 0, itemData); + return isEntity() || itemId < 0 ? null : + (itemData <= Byte.MAX_VALUE ? + new ItemStack(itemId, 1, (short) itemData) + : new ItemStack(itemId, 1, itemData)); + } + + public ItemStack toItemStack(int amount) { + //return isEntity() || itemId < 0 ? null : new ItemStack(itemId, amount, (short) 0, itemData); + return isEntity() || itemId < 0 ? null : + (itemData <= Byte.MAX_VALUE ? + new ItemStack(itemId, amount, (short) itemData) + : new ItemStack(itemId, amount, itemData)); + } + + // creatures are numbered starting at 4000 + public boolean isEntity() { + return itemId >= 4000 && itemId < 5000; + } + + public static JItems getItem(int id) { + for (JItems i : JItems.values()) { + if (i.ID() == id && i.Data() == 0) { + return i; + } + } + return null; + } + + public static JItems getItem(int id, short dat) { + for (JItems i : JItems.values()) { + if (i.ID() == id && i.Data() == dat) { + return i; + } + } + return null; + } + + public static JItems getItem(Material material) { + for (JItems i : JItems.values()) { + if (i.ID() == material.getId() && i.Data() == 0) { + return i; + } + } + return null; + } + + public static JItems findItem(Item search) { + if (search == null || search.getItemStack() == null) { + return null; + } + int id = search.getItemStack().getTypeId(); + short dat = search.getItemStack().getData().getData(); + for (JItems i : JItems.values()) { + if (i.ID() == id && i.Data() == dat) { + return i; + } + } + return null; + } + + public static int getMaxStack(int id) { + JItems i = getItem(id); + return i == null ? 64 : i.MaxStackSize(); + } + + public static int getMaxStack(int id, short dat) { + JItems i = getItem(id, dat); + return i == null ? 64 : i.MaxStackSize(); + } + + public static int getMaxStack(ItemStack it) { + JItems i = getItem(it.getTypeId(), it.getData() == null ? 0 : it.getData().getData()); + return i == null ? 64 : i.MaxStackSize(); + } + + public static boolean isStackable(ItemStack it) { + int i = Arrays.binarySearch(unsafeStack, it.getTypeId()); + return i < 0 && !isTool(it.getTypeId()); + } + + public static boolean isStackable(int id) { + int i = Arrays.binarySearch(unsafeStack, id); + return i < 0 && !isTool(id); + } + + public static boolean isTool(int id) { + int i = Arrays.binarySearch(tools, id); + return i >= 0 && tools[i] == id; + } + + public static boolean isTool(ItemStack it) { + JItems ji = getItem(it.getTypeId(), it.getData() == null ? 0 : it.getData().getData()); + return ji != null && ji.IsTool(); + } + + public static boolean hasData(int id) { + JItems i = getItem(id); + return i != null && i.hasData; + } + + @Override + public String toString() { + return String.format("%s (%d:%d)", getName(), itemId, itemData); + } + + public boolean equals(ItemStack i) { + return i != null + && itemId == i.getTypeId() + && (IsTool() || (!hasData || (hasData && itemData == i.getDurability()))); + } +} // end class JItems + diff --git a/src/me/jascotty2/lib/bukkit/item/Kit.java b/src/me/jascotty2/lib/bukkit/item/Kit.java new file mode 100644 index 0000000..185abe9 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/Kit.java @@ -0,0 +1,233 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: provides a class for kits (collection of items) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import me.jascotty2.lib.io.CheckInput; +import java.util.LinkedList; +import org.bukkit.inventory.ItemStack; + +public class Kit extends JItem { + + private LinkedList kititems = new LinkedList(); + + public class KitItem extends JItem { + + public int itemAmount; + + public KitItem() { + } + + public KitItem(JItem i, int amt) { + // make sure is a legal (existing) item + //JItem n = JItem.findItem(i); + if (i != null && i.item != null) { + SetItem(i); + itemAmount = amt; + } else { + itemAmount = 0; + } + } + + public JItem toItem() { + return new JItem(this); + } + + public boolean iequals(ItemStack i) { + return item != null && item.equals(i); + } + + @Override + public String toString() { + return String.format("%s (%d:%d) @%d", item == null ? name : item.getName(), item.ID(), item.Data(), itemAmount); + } + } + + public Kit() { + } // end default constructor + + public Kit(JItem copy) { + AddItem(copy); + } + + public Kit(JItem[] copy) { + for (JItem i : copy) { + AddItem(i); + } + } + + public Kit(JItem copy, int itemAmount) { + AddItem(copy, itemAmount); + } + + public Kit(JItem[] copy, int itemAmount) { + for (JItem i : copy) { + AddItem(i, itemAmount); + } + } + + public Kit(String kitStr) { + SetItems(fromStr(kitStr)); + } + + public static Kit fromStr(String kitStr) { + if (kitStr == null) { + return null; + } + Kit ret = new Kit(); + String all[] = kitStr.replace(" ", "").split(","); + for (String i : all) { + if (i.contains("@")) { + if (i.length() > i.indexOf("@")) { + ret.AddItem(JItemDB.findItem(i.substring(0, i.indexOf("@"))), + CheckInput.GetInt(i.substring(i.indexOf("@") + 1), 1)); + } else { + ret.AddItem(JItemDB.findItem(i.substring(0, i.indexOf("@")))); + } + } else { + ret.AddItem(JItemDB.findItem(i)); + } + } + if (ret.numItems() == 0) { + return null; + } + return ret; + } + + public final void AddItem(JItem toAdd) { + if (toAdd != null) { + KitItem t = new KitItem(toAdd, 1); + if (t.itemAmount > 0) { + kititems.add(t); + } + } + } + + public final void AddItem(JItem toAdd, int itemAmount) { + if (itemAmount <= 0 || toAdd == null) { + return; + } + KitItem t = new KitItem(toAdd, itemAmount); + if (t.itemAmount > 0) { + kititems.add(t); + } + } + + /** + * adds a new item without checking if it's a valid item + * @param toAdd + */ + public final void AddNewItem(JItem toAdd) { + if (toAdd == null) { + return; + } + KitItem t = new KitItem(); + t.itemAmount = 1; + t.SetItem(toAdd); + kititems.add(t); + } + + /** + * adds a new item without checking if it's a valid item + * @param toAdd + * @param itemAmount + */ + public final void AddNewItem(JItem toAdd, int itemAmount) { + if (itemAmount <= 0 || toAdd == null) { + return; + } + KitItem t = new KitItem(); + t.itemAmount = itemAmount; + t.SetItem(toAdd); + kititems.add(t); + } + + public final void SetItems(Kit copy) { + kititems.clear(); + if (copy != null) { + kititems.addAll(copy.kititems); + } + } + + public int numItems() { + return kititems.size(); + } + + public int totalItems() { + int n = 0; + for (KitItem k : kititems) { + n += k.itemAmount; + } + return n; + } + + public JItem getItem(int index) { + if (index >= 0 && index < kititems.size()) { + return kititems.get(index).toItem(); + } + return null; + } + + public int getItemCount(int index) { + if (index >= 0 && index < kititems.size()) { + return kititems.get(index).itemAmount; + } + return 0; + } + + public int getItemCount(JItem i) { + for (KitItem k : kititems) { + if (k.equals(i)) { + return k.itemAmount; + } + } + return 0; + } + + public JItem[] getItems() { + return kititems.toArray(new JItem[0]); + } + + public KitItem getKitItem(int i) { + if (i >= 0 && i < kititems.size()) { + return kititems.get(i); + } + return null; + } + + public KitItem[] getKitItems() { + return kititems.toArray(new KitItem[0]); + } + + @Override + public boolean isKit(){ + return true; + } + + @Override + public String toString() { + String kitid = name + ": "; + for (int i = 0; i < kititems.size(); ++i) { + kitid += kititems.get(i).coloredName() + "@" + kititems.get(i).itemAmount; + if (i + 1 < kititems.size()) { + kitid += " + "; + } + } + return kitid; + } +} // end class Kit + diff --git a/src/me/jascotty2/lib/bukkit/item/OldEntityType.java b/src/me/jascotty2/lib/bukkit/item/OldEntityType.java new file mode 100644 index 0000000..ca2c434 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/OldEntityType.java @@ -0,0 +1,214 @@ +/* + * copy of EntityType.java, as of compile time + * https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/entity/EntityType.java + */ +package me.jascotty2.lib.bukkit.item; + +import org.bukkit.entity.*; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Location; +import org.bukkit.World; + +public enum OldEntityType { + // These strings MUST match the strings in nms.EntityTypes and are case sensitive. + /** + * An item resting on the ground. + * + * Spawn with {@link World#dropItem(Location, ItemStack)} + * or {@link World#dropItemNaturally(Location, ItemStack)} + */ + DROPPED_ITEM("Item", Item.class, 1, false), + /** + * An experience orb. + */ + EXPERIENCE_ORB("XPOrb", ExperienceOrb.class, 2), + /** + * A painting on a wall. + */ + PAINTING("Painting", Painting.class, 9), + /** + * An arrow projectile; may get stuck in the ground. + */ + ARROW("Arrow", Arrow.class, 10), + /** + * A flyinf snowball. + */ + SNOWBALL("Snowball", Snowball.class, 11), + /** + * A flying large fireball, as thrown by a Ghast for example. + */ + FIREBALL("Fireball", LargeFireball.class, 12), + /** + * A flying small fireball, such as thrown by a Blaze or player. + */ + SMALL_FIREBALL("SmallFireball", SmallFireball.class, 13), + /** + * A flying ender pearl. + */ + ENDER_PEARL("ThrownEnderpearl", EnderPearl.class, 14), + /** + * An ender eye signal. + */ + ENDER_SIGNAL("EyeOfEnderSignal", EnderSignal.class, 15), + /** + * A flying experience bottle. + */ + THROWN_EXP_BOTTLE("ThrownExpBottle", ThrownExpBottle.class, 17), + /** + * An item frame on a wall. + */ + ITEM_FRAME("ItemFrame", ItemFrame.class, 18), + /** + * A flying wither skull projectile. + */ + WITHER_SKULL("WitherSkull", WitherSkull.class, 19), + /** + * Primed TNT that is about to explode. + */ + PRIMED_TNT("PrimedTnt", TNTPrimed.class, 20), + /** + * A block that is going to or is about to fall. + */ + FALLING_BLOCK("FallingSand", FallingBlock.class, 21, false), + FIREWORK("FireworksRocketEntity", Firework.class, 22, false), + /** + * A placed minecart of any type. + */ + MINECART("Minecart", Minecart.class, 40), + /** + * A placed boat. + */ + BOAT("Boat", Boat.class, 41), + CREEPER("Creeper", Creeper.class, 50), + SKELETON("Skeleton", Skeleton.class, 51), + SPIDER("Spider", Spider.class, 52), + GIANT("Giant", Giant.class, 53), + ZOMBIE("Zombie", Zombie.class, 54), + SLIME("Slime", Slime.class, 55), + GHAST("Ghast", Ghast.class, 56), + PIG_ZOMBIE("PigZombie", PigZombie.class, 57), + ENDERMAN("Enderman", Enderman.class, 58), + CAVE_SPIDER("CaveSpider", CaveSpider.class, 59), + SILVERFISH("Silverfish", Silverfish.class, 60), + BLAZE("Blaze", Blaze.class, 61), + MAGMA_CUBE("LavaSlime", MagmaCube.class, 62), + ENDER_DRAGON("EnderDragon", EnderDragon.class, 63), + WITHER("WitherBoss", Wither.class, 64), + BAT("Bat", Bat.class, 65), + WITCH("Witch", Witch.class, 66), + PIG("Pig", Pig.class, 90), + SHEEP("Sheep", Sheep.class, 91), + COW("Cow", Cow.class, 92), + CHICKEN("Chicken", Chicken.class, 93), + SQUID("Squid", Squid.class, 94), + WOLF("Wolf", Wolf.class, 95), + MUSHROOM_COW("MushroomCow", MushroomCow.class, 96), + SNOWMAN("SnowMan", Snowman.class, 97), + OCELOT("Ozelot", Ocelot.class, 98), + IRON_GOLEM("VillagerGolem", IronGolem.class, 99), + VILLAGER("Villager", Villager.class, 120), + ENDER_CRYSTAL("EnderCrystal", EnderCrystal.class, 200), + // These don't have an entity ID in nms.EntityTypes. + /** + * A flying splash potion. + */ + SPLASH_POTION(null, ThrownPotion.class, -1, false), + /** + * A flying chicken egg. + */ + EGG(null, Egg.class, -1, false), + /** + * A fishing line and bobber. + */ + FISHING_HOOK(null, Fish.class, -1, false), + /** + * A bolt of lightning. + * + * Spawn with {@link World#strikeLightning(Location)}. + */ + LIGHTNING(null, LightningStrike.class, -1, false), + WEATHER(null, Weather.class, -1, false), + PLAYER(null, Player.class, -1, false), + COMPLEX_PART(null, ComplexEntityPart.class, -1, false), + /** + * An unknown entity without an Entity Class + */ + UNKNOWN(null, null, -1, false); + + + private String name; + private Class clazz; + private short typeId; + private boolean independent, living; + + private static final Map NAME_MAP = new HashMap(); + private static final Map ID_MAP = new HashMap(); + + static { + for (OldEntityType type : values()) { + if (type.name != null) { + NAME_MAP.put(type.name.toLowerCase(), type); + } + if (type.typeId > 0) { + ID_MAP.put(type.typeId, type); + } + } + } + + private OldEntityType(String name, Class clazz, int typeId) { + this(name, clazz, typeId, true); + } + + private OldEntityType(String name, Class clazz, int typeId, boolean independent) { + this.name = name; + this.clazz = clazz; + this.typeId = (short) typeId; + this.independent = independent; + if (clazz != null) { + this.living = LivingEntity.class.isAssignableFrom(clazz); + } + } + + public String getName() { + return name; + } + + public Class getEntityClass() { + return clazz; + } + + public short getTypeId() { + return typeId; + } + + public static OldEntityType fromName(String name) { + if (name == null) { + return null; + } + return NAME_MAP.get(name.toLowerCase()); + } + + public static OldEntityType fromId(int id) { + if (id > Short.MAX_VALUE) { + return null; + } + return ID_MAP.get((short) id); + } + + /** + * Some entities cannot be spawned using {@link World#spawnCreature(Location, OldEntityType)} + * or {@link World#spawn(Location, Class)}, usually + * because they require additional information in order to spawn. + * @return False if the entity type cannot be spawned + */ + public boolean isSpawnable() { + return independent; + } + + public boolean isAlive() { + return living; + } +} \ No newline at end of file diff --git a/src/me/jascotty2/lib/bukkit/item/PriceListItem.java b/src/me/jascotty2/lib/bukkit/item/PriceListItem.java new file mode 100644 index 0000000..671a52f --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/item/PriceListItem.java @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: structure to hold table data + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.item; + +import java.util.Date; + +public class PriceListItem extends JItem { + + public double buy, sell; + protected Date tempCacheDate = null; // when last updated + + public PriceListItem() { + buy = sell = -1; + } // end default constructor + + /** + * does not make a check if the item is valid + * @param num item id + * @param sub sub-data + * @param name name of the item + * @param buyPrice + * @param sellPrice + */ + public PriceListItem(int num, byte sub, String name, double buyPrice, double sellPrice) { + super(num, sub); + if (super.name == null) { + super.name = name; + } + buy = buyPrice; + sell = sellPrice; + tempCacheDate = new Date(); + } + + public PriceListItem(JItem copy, double buyPrice, double sellPrice) { + super(copy); + buy = buyPrice; + sell = sellPrice; + tempCacheDate = new Date(); + } + + public PriceListItem(JItem toSave) { + super(toSave); + if (toSave != null) { + tempCacheDate = new Date(); + } + } + + public PriceListItem(PriceListItem copy) { + super(copy); + if (copy != null) { + buy = copy.buy; + sell = copy.sell; + tempCacheDate = new Date(); + } + } + + public double BuyPrice() { + return buy; + } + + public double SellPrice() { + return sell; + } + + public Date LastUpdated() { + return tempCacheDate; + } + + public void SetBuyPrice(double newPrice) { + buy = newPrice; + tempCacheDate = new Date(); + } + + public void SetSellPrice(double newPrice) { + sell = newPrice; + tempCacheDate = new Date(); + } + + public void SetPrice(double buy, double sell) { + this.buy = buy; + this.sell = sell; + tempCacheDate = new Date(); + } + + public void SetFromItem(JItem i) { + SetItem(i); + tempCacheDate = new Date(); + } + + public static PriceListItem fromItem(JItem i) { + if (i == null) { + return null; + } + return new PriceListItem(i); + } + + public long getTime() { + return tempCacheDate.getTime(); + } + + public void Set(PriceListItem copy) { + SetItem(copy); + if (copy != null) { + this.buy = copy.buy; + this.sell = copy.sell; + } + tempCacheDate = new Date(); + } + + public boolean equals(PriceListItem i) { + return i != null && item != null && item.equals(i.item); + } + /* + + @Override + public boolean equals(Object o) { + if (o instanceof PriceListItem || o instanceof JItem) { + return equals((PriceListItem) o); + } + return false; + } + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + (int) (Double.doubleToLongBits(this.buy) ^ (Double.doubleToLongBits(this.buy) >>> 32)); + hash = 89 * hash + (int) (Double.doubleToLongBits(this.sell) ^ (Double.doubleToLongBits(this.sell) >>> 32)); + return hash; + }*/ +} // end class PriceListRow + diff --git a/src/me/jascotty2/lib/bukkit/shop/Discount.java b/src/me/jascotty2/lib/bukkit/shop/Discount.java new file mode 100644 index 0000000..5abf112 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/Discount.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: holder for discount data + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.bukkit.item.JItem; +import java.util.LinkedList; + +public class Discount { + + public boolean isUser; // (if false, is group) + public LinkedList users = new LinkedList(); // which users/groups have this discount + public LinkedList items; // which items are discounted (null == all) + public double discount; // discount percentage + + public Discount(JItem discounted, boolean isUser, LinkedList users, double discount) { + this.isUser = isUser; + this.discount = discount; + this.users.clear(); + this.users.addAll(users); + items = new LinkedList(); + items.add(discounted); + } + + public Discount(boolean isUser, String user, double discount) { + this.isUser = isUser; + this.discount = discount; + this.users.clear(); + this.users.add(user); + } + + public Discount() { + } + + public void addUser(String usr){ + if(usr!=null && usr.length()>0){ + users.add(usr); + } + } + public void addItem(JItem itm){ + if(itm!=null){ + if(items==null)items = new LinkedList(); + items.add(itm); + } + } + + public void removeUser(String usr){ + users.remove(usr); + } + public void removeItem(JItem itm){ + if(items!=null){ + items.remove(itm); + if(items.size()==0) items=null; + } + } + + public String getUserListStr() { + String ret = ""; + for (int i = 0; i < users.size(); ++i) { + ret += users.get(i); + if (i + 1 < users.size()) { + ret += ","; + } + } + return ret; + } + + public String getItemListStr() { + String ret = ""; + if (items != null) { + for (int i = 0; i < items.size(); ++i) { + ret += items.get(i).IdDatStr(); + if (i + 1 < items.size()) { + ret += ","; + } + } + } + return ret; + } +} diff --git a/src/me/jascotty2/lib/bukkit/shop/ItemStock.java b/src/me/jascotty2/lib/bukkit/shop/ItemStock.java new file mode 100644 index 0000000..d435653 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/ItemStock.java @@ -0,0 +1,540 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: provides a way to track how much of an item is avaliable + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.io.FileIO; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.mysql.MySQLItemStock; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.lib.mysql.MySQL; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class ItemStock { + +// set this to true to catch & log exceptions instead of throwing them + public static boolean log_nothrow = false; + // what to write to if logging + protected final static Logger logger = Logger.getLogger("Minecraft"); + //how long before tempCache is considered outdated (seconds), used if MySQL & caching disabled + public int tempCacheTTL = 10; + // temporary storage for last queried item (primarily for when caching is disabled) + ItemStockEntry tempCache = null; + // use full db caching? + public boolean useCache = false; + // if using caching, how long before cache is considered outdated (seconds) + public long dbCacheTTL = 0; + // if false, will disconnect from db when now using it (use if have a high dbCacheTTL) + //public boolean persistentMySQL = true; + // last time the stockList was updated + protected Date lastCacheUpdate = null; + protected ArrayList stockList = new ArrayList(); + private boolean isLoaded = false; + protected DBType databaseType = DBType.FLATFILE; + // current db connection, if using MySQL + protected MySQLItemStock MySQLstockList = null; + // file, if flatfile + protected File flatFile = null; + + /* + public static > List asSortedList(Collection c) { + List list = new ArrayList(c); + java.util.Collections.sort(list); + return list; + }//*/ + public static class ItemStockEntryComparator implements Comparator { + + boolean descending = true; + + public ItemStockEntryComparator() { + } + + public int compare(ItemStockEntry o1, ItemStockEntry o2) { + return (o1.itemNum * 100 - o2.itemNum * 100 + o1.itemSub - o2.itemSub) * (descending ? 1 : -1); + } + } + + public void sort() { + //stockList=Arrays.sort(stockList.toArray()); + java.util.Collections.sort(stockList, new ItemStockEntryComparator()); + } + + public enum DBType { + + MYSQL, FLATFILE // SQLITE, + } + + public ItemStock() { + } // end default constructor + + public String stockListName() { + return databaseType == DBType.MYSQL + ? (MySQLstockList == null ? null + : (MySQLstockList.getHostName() + ":" + + MySQLstockList.getDatabaseName() + "/" + + MySQLstockList.getTableName())) : flatFile == null ? null : flatFile.getName(); + } + + public MySQL getMySQLconnection() { + return MySQLstockList == null ? null : MySQLstockList; + } + + public final boolean reloadList() throws SQLException, IOException, Exception { + if (databaseType == DBType.MYSQL) { + return reloadMySQL(); + } else { + return loadFile(flatFile); + } + } + + private boolean reloadMySQL() throws SQLException, Exception { // + if (MySQLstockList != null)// && MySQLdatabase.isConnected()) MySQLdatabase.disconnect(); + { + stockList.clear(); + try { + MySQLstockList.connect(); + } catch (Exception ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Error connecting to MySQL database", ex); + } else { + throw new Exception("Error connecting to MySQL database", ex); + } + } + // load stockList data + stockList.addAll(MySQLstockList.getFullList()); + } + + return false; + } + + public final boolean loadMySQL(String database, String tableName, String username, String password, String hostName, String portNum) throws SQLException, Exception { + databaseType = DBType.MYSQL; + try { + if (MySQLstockList == null) { + MySQLstockList = new MySQLItemStock(database, tableName, username, password, hostName, portNum); + } else { + MySQLstockList.connect(database, tableName, username, password, hostName, portNum); + } + } catch (SQLException ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Error connecting to MySQL database or while retrieving table list", ex); + } else { + throw ex; // "Error connecting to MySQL database or while retrieving table list" + } + } catch (Exception ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Failed to start database connection", ex); + } else { + throw ex; // "Failed to start database connection" + } + } + return isLoaded = MySQLstockList.isConnected(); + } + + public boolean loadFile(File toload) throws IOException, Exception { + if (toload != null) { + databaseType = DBType.FLATFILE; + flatFile = toload; + stockList.clear(); + if (toload.exists()) { + isLoaded = false; + + try { + List actFile = FileIO.loadCSVFile(flatFile); + for (int n = 0; n < actFile.size(); ++n) {//String[] line : actFile) { + String[] line = actFile.get(n); + if (line.length == 4) { + JItem plItem = JItemDB.findItem(line[0] + ":" + (line[1].equals(" ") ? "0" : line[1])); + if (plItem != null) { + stockList.add(new ItemStockEntry(plItem, CheckInput.GetLong(line[3], 0))); + } else if (n > 0) { // first line is expected invalid: is title + logger.log(Level.WARNING, String.format("Invalid item on line %d in %s", (n + 1), toload.getName())); + } + } else { + logger.log(Level.WARNING, String.format("unexpected stockList line at %d in %s", (n + 1), toload.getName())); + } + } + } catch (FileNotFoundException ex) { + if (!log_nothrow) { + throw new IOException("Unexpected Error: File not found: " + flatFile.getName(), ex); + } + logger.log(Level.SEVERE, "Unexpected Error: File not found: " + flatFile.getName(), ex); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error opening " + toload.getName() + " for reading", ex); + } + logger.log(Level.SEVERE, "Error opening " + toload.getName() + " for reading", ex); + } + return isLoaded = true && save(); + } else { // !toload.exists() + // still set as loaded + isLoaded = true; + return save(); + } + } + return false; + } + + /** + * closes connections & frees up used memory + * @throws IOException + * @throws SQLException + */ + public void close() throws IOException, SQLException { + // no need to save.. is saved after every edit + //save(); + stockList.clear(); + lastCacheUpdate = null; + isLoaded = false; + if (databaseType == DBType.MYSQL) { + MySQLstockList.commit(); + MySQLstockList.disconnect(); + MySQLstockList = null; + } + } + + public boolean IsLoaded() { + if (databaseType == DBType.MYSQL) { + return (dbCacheTTL == 0 && MySQLstockList.isConnected()) || isLoaded; + } + return isLoaded; + } + + public boolean save() throws IOException, SQLException { + sort(); + if (databaseType == DBType.MYSQL) { + try { + MySQLstockList.commit(); + return true; + } catch (SQLException ex) { + if (!log_nothrow) { + throw new SQLException("Error executing COMMIT", ex); + } + logger.log(Level.SEVERE, "Error executing COMMIT", ex); + return false; + } + } else {// if(flatFile != null) { + try { + return saveFile(flatFile); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error Saving " + (flatFile == null ? "flatfile stock database" : flatFile.getName()), ex); + } + logger.log(Level.SEVERE, "Error Saving " + (flatFile == null ? "flatfile stock database" : flatFile.getName()), ex); + return false; + } + } + } + + public boolean saveFile(File tosave) throws IOException { + if (tosave != null && !tosave.isDirectory()) { + if (!tosave.exists() || tosave.canWrite()) { + try { + ArrayList lines = new ArrayList(); + lines.add("id,subdata,name,amount"); + for (ItemStockEntry i : stockList) { + // names provided for others to easily edit db + lines.add(i.itemNum + "," + i.itemSub + "," + i.name + "," + i.amount); + } + if (!FileIO.saveFile(flatFile, lines)) { + if (!log_nothrow) { + throw new IOException("Error writing to " + flatFile.getName()); + } + logger.log(Level.SEVERE, String.format("Error writing to %s", flatFile.getName())); + } + } catch (FileNotFoundException ex) { + if (!log_nothrow) { + throw new IOException("Unexpected Error: File not found: " + tosave.getName(), ex); + } + logger.log(Level.SEVERE, "Unexpected Error: File not found: " + tosave.getName(), ex); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error opening " + tosave.getName() + " for writing", ex); + } + logger.log(Level.SEVERE, "Error opening " + tosave.getName() + " for writing", ex); + } + + return true; + } + } else if (tosave == null) { + throw new IOException("no file to save to"); + } else if (tosave.isDirectory()) { + throw new IOException("file to save is a directory"); + } + return false; + } + + public void updateCache() throws SQLException, Exception { + updateCache(true); + } + + public void updateCache(boolean checkFirst) throws SQLException, Exception { + if (databaseType != DBType.MYSQL) { + // (flatfile is cached, manually updated) + return; + } + //System.out.println("use c:" + useCache + " " + dbCacheTTL); + if (!checkFirst || (useCache && lastCacheUpdate == null) + || (useCache && dbCacheTTL > 0 && lastCacheUpdate != null + && ((new Date()).getTime() - lastCacheUpdate.getTime()) / 1000 > dbCacheTTL)) { + //System.out.println("updating cache (" + (lastCacheUpdate != null?((new Date()).getTime() - lastCacheUpdate.getTime())/100:-1) + "s since last update)"); + // MySQL cache outdated: update + List update = MySQLstockList.getFullList(); + stockList.clear(); + stockList.addAll(update); + lastCacheUpdate = new Date(); + } + //else cache up-to-date, or disabled + } + + public boolean ItemExists(String check) throws SQLException, Exception { + return ItemExists(JItemDB.findItem(check)); + } + + public boolean ItemExists(ItemStack check) throws SQLException, Exception { + return ItemExists(JItemDB.findItem(check)); + } + + public boolean ItemExists(JItem check) throws SQLException, Exception { + if (check == null) { + return false; + } + updateCache(true); + if (databaseType == DBType.MYSQL && dbCacheTTL == 0) { + tempCache = MySQLstockList.getItem(check); + } else { // should be in cache + int i = stockList.indexOf(new ItemStockEntry(check)); + if (i < 0) { + return false; + }//else + if (tempCache == null) { + tempCache = new ItemStockEntry(stockList.get(i)); + } else { + tempCache.Set(stockList.get(i)); + } + } + return tempCache != null; + } + + public long getItemAmount(ItemStack i) throws SQLException, Exception { + return getItemAmount(JItemDB.findItem(i)); + } + + public long getItemAmount(String s) throws SQLException, Exception { + return getItemAmount(JItemDB.findItem(s)); + } + + public long getItemAmount(JItem it) throws SQLException, Exception { + if (it == null) { + return -1; + } + ItemStockEntry itp = getItemEntry(it); + if (itp != null) { + return itp.amount; + } + return -1; + } + + public long getItemAmount(int id) throws SQLException, Exception { + return getItemAmount(id, (byte) 0); + } + + public long getItemAmount(int id, byte dat) throws SQLException, Exception { + ItemStockEntry itp = getItemEntry(id, dat); + if (itp != null) { + return itp.amount; + } + return -1; + } + + public void changeItemAmount(ItemStack i, long delta) throws SQLException, Exception { + changeItemAmount(JItemDB.findItem(i), delta); + } + + public void changeItemAmount(String s, long delta) throws SQLException, Exception { + changeItemAmount(JItemDB.findItem(s), delta); + } + + public void changeItemAmount(JItem it, long delta) throws SQLException, Exception { + if (it == null) { + return; + } + ItemStockEntry itp = getItemEntry(it); + if (itp != null) { + setItemAmount(it, itp.amount + delta); + } + } + + public void changeItemAmount(int id, String name, long delta) throws SQLException, Exception { + ItemStockEntry itp = getItemEntry(id, (byte) 0); + if (itp != null) { + setItemAmount(id, (byte) 0, name, itp.amount + delta); + } + } + + public void changeItemAmount(int id, byte dat, String name, long delta) throws SQLException, Exception { + ItemStockEntry itp = getItemEntry(id, dat); + if (itp != null) { + setItemAmount(id, dat, name, itp.amount + delta); + } + } + + public ItemStockEntry getItemEntry(JItem it) throws SQLException, Exception { + if (it == null) { + return null; + } else { + return getItemEntry(it.ID(), it.Data()); + } + } + + public ItemStockEntry getItemEntry(int id, int dat) throws SQLException, Exception { + if (tempCache != null && tempCache.itemNum == id && tempCache.itemSub == dat + && ((new Date()).getTime() - tempCache.getTime()) / 1000 < tempCacheTTL) { + // use temp + return tempCache; + } + if (databaseType == DBType.MYSQL && !useCache) { + tempCache = MySQLstockList.getItem(id, dat); + } else { + updateCache(true); + ItemStockEntry t = _getItemEntry(id, dat); + if (t == null) { + return null; + } else if (tempCache == null) { + tempCache = new ItemStockEntry(t); + } else { + tempCache.Set(t); + } + //System.out.println(tempCache); + } + return tempCache; + } + + private ItemStockEntry _getItemEntry(int id, int dat) { + for (ItemStockEntry i : stockList) { + if (i.itemNum == id && i.itemSub == dat) { + return i; + } + } + return null; + } + + private int _getItemEntryIndex(int id, int dat) { + for (int i = 0; i < stockList.size(); ++i) { + ItemStockEntry t = stockList.get(i); + if (t.itemNum == id && t.itemSub == dat) { + return i; + } + } + return -1; + } + + public boolean setItemAmount(String item, long a) throws SQLException, IOException, Exception { + return setItemAmount(JItemDB.findItem(item), a); + } + + public boolean setItemAmount(JItem it, long a) throws SQLException, IOException, Exception { + if (it == null) {// ||(b < 0 && s < 0)) { + return false; + } + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLstockList.setAmount(it, a); + updateCache(false); + return true; + } else { + int i = _getItemEntryIndex(it.ID(), it.Data()); + if (i < 0) { + stockList.add(new ItemStockEntry(it, a)); + } else { + stockList.get(i).SetAmount(a); + } + return save(); + } + } + + public boolean setItemAmount(int id, byte dat, String name, long a) throws SQLException, IOException, Exception { + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLstockList.setAmount(id, dat, name, a); + updateCache(false); + return true; + } else { + int i = _getItemEntryIndex(id, dat); + if (i < 0) { + stockList.add(new ItemStockEntry(id, dat, name, a)); + } else { + stockList.get(i).SetAmount(a); + } + return save(); + } + } + + public boolean remove(String s) throws SQLException, Exception { + return remove(JItemDB.findItem(s)); + } + + public boolean remove(JItem it) throws SQLException, Exception { + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLstockList.removeItem(it); + updateCache(false); + return true; + } else { + if (stockList.remove(new ItemStockEntry(it))) { + return save(); + } + } + return false; + } + + public void clearAll(boolean save) throws IOException, SQLException{ + stockList.clear(); + if(save){ + if (databaseType == DBType.MYSQL) { + MySQLstockList.clearDB(); + } else { + save(); + } + } + } + + public ItemStockEntry[] getstockListItems() throws SQLException, Exception { + if (databaseType == DBType.MYSQL && !useCache) { + updateCache(false);// manually update + } else { + updateCache(true); + } + return stockList.toArray(new ItemStockEntry[0]); + } +} // end class ItemStock + diff --git a/src/me/jascotty2/lib/bukkit/shop/PriceList.java b/src/me/jascotty2/lib/bukkit/shop/PriceList.java new file mode 100644 index 0000000..b09a223 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/PriceList.java @@ -0,0 +1,870 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: tracks items in a database with buy/sell prices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.mysql.MySQLPriceList; +import me.jascotty2.lib.bukkit.item.*; +import me.jascotty2.lib.bukkit.MinecraftChatStr; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.lib.mysql.MySQL; +import org.bukkit.inventory.ItemStack; + +public class PriceList { + // set this to true to catch & log exceptions instead of throwing them + + public static boolean log_nothrow = false; + // what to write to if logging + protected final static Logger logger = Logger.getLogger("Minecraft"); + //how long before tempCache is considered outdated (seconds), used if MySQL & caching disabled + public int tempCacheTTL = 10; + // temporary storage for last queried item (primarily for when caching is disabled) + PriceListItem tempCache = null; + // use full db caching? + public boolean useCache = false; + // if using caching, how long before cache is considered outdated (seconds) + public long dbCacheTTL = 0; + // if false, will disconnect from db when now using it (use if have a high dbCacheTTL) + //public boolean persistentMySQL = true; + // last time the pricelist was updated + protected Date lastCacheUpdate = null; + protected List priceList = new ArrayList(); + private boolean isLoaded = false; + private DBType databaseType = DBType.FLATFILE; + // current db connection, if using MySQL + protected MySQLPriceList MySQLpricelist = null; + // file, if flatfile + File flatFile = null; + // max buy/sell price + public static final int MAX_PRICE = 999999999; // int max=2,147,483,647 + // uses the JItem.IdDatStr() (like "5:0") + public List sortOrder = new ArrayList(); + + /* + public static > List asSortedList(Collection c) { + List list = new ArrayList(c); + java.util.Collections.sort(list); + return list; + }//*/ + public static class PriceListItemComparator implements Comparator { + + public List sortOrder = new ArrayList(); + boolean descending = true; + + public PriceListItemComparator() { + } + + public PriceListItemComparator(List sortOrder) { + this.sortOrder.addAll(sortOrder); + } + + public int compare(PriceListItem o1, PriceListItem o2) { + if (sortOrder.size() > 0) { + int o1i = sortOrder.indexOf(o1.IdDatStr()); + int o2i = sortOrder.indexOf(o2.IdDatStr()); + if (o1i != -1 && o2i != -1) { + return o2i - o1i; + } else if (o1i != -1) { + return -1; + } else if (o2i != -1) { + return 1; + } + } + return (o1.ID() * 100 - o2.ID() * 100 + o1.Data() - o2.Data()) * (descending ? 1 : -1); + } + } + + public void sort() { + java.util.Collections.sort(priceList, new PriceListItemComparator(sortOrder)); + } + + public enum DBType { + + MYSQL, FLATFILE // SQLITE, + } + + public PriceList() { + } // end default constructor + + public String pricelistName() { + return databaseType == DBType.MYSQL + ? (MySQLpricelist == null ? null + : (MySQLpricelist.getHostName() + ":" + + MySQLpricelist.getDatabaseName() + "/" + + MySQLpricelist.getTableName())) : flatFile == null ? null : flatFile.getName(); + } + + public MySQL getMySQLconnection() { + return MySQLpricelist == null ? null : MySQLpricelist; + } + + public final boolean reloadList() throws SQLException, IOException, Exception { + if (databaseType == DBType.MYSQL) { + return reloadMySQL(); + } else { + return loadFile(flatFile); + } + } + + private boolean reloadMySQL() throws SQLException, Exception { // + if (MySQLpricelist != null)// && MySQLdatabase.IsConnected()) MySQLdatabase.disconnect(); + { + priceList.clear(); + try { + MySQLpricelist.connect(); + } catch (Exception ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Error connecting to MySQL database", ex); + } else { + throw new Exception("Error connecting to MySQL database", ex); + } + } + // load pricelist data + priceList.addAll(MySQLpricelist.getFullList()); + } + + return false; + } + + public final boolean loadMySQL(String database, String tableName, String username, String password, String hostName, String portNum) throws SQLException, Exception { + databaseType = DBType.MYSQL; + try { + if (MySQLpricelist == null) { + MySQLpricelist = new MySQLPriceList(database, tableName, username, password, hostName, portNum); + } else { + MySQLpricelist.connect(database, tableName, username, password, hostName, portNum); + } + } catch (SQLException ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Error connecting to MySQL database or while retrieving table list", ex); + } else { + throw ex; // "Error connecting to MySQL database or while retrieving table list" + } + } catch (Exception ex) { + if (log_nothrow) { + logger.log(Level.SEVERE, "Failed to start database connection", ex); + } else { + throw ex; // "Failed to start database connection" + } + } + return isLoaded = MySQLpricelist.isConnected(); + } + + public boolean loadFile(File toload) throws IOException, Exception { + if (toload != null) { + databaseType = DBType.FLATFILE; + flatFile = toload; + priceList.clear(); + if (toload.exists()) { + isLoaded = false; + FileReader fstream = null; + try { + fstream = new FileReader(toload.getAbsolutePath()); + BufferedReader in = new BufferedReader(fstream); + try { + int n = 0; + for (String line = null; (line = in.readLine()) != null && line.length() > 0; ++n) { + // if was edited in openoffice, will instead have semicolins.. + String fields[] = line.replace(";", ",").replace(",,", ", ,").split(","); + + if (fields.length > 4) { + if(n == 0 && fields[0].equals("id")){ + continue; + } + JItem plItem = JItemDB.findItem(fields[0] + ":" + (fields[1].equals(" ") ? "0" : fields[1])); + if (plItem != null) { + //priceList.add(new PriceListItem(plItem, fields[2].length() == 0 ? -1 : CheckInput.GetDouble(fields[2], -1), fields[3].length() == 0 ? -1 : CheckInput.GetDouble(fields[3], -1))); + priceList.add(new PriceListItem(plItem, + fields[2].equals(" ") ? -1 : CheckInput.GetDouble(fields[2], -1), + fields[3].equals(" ") ? -1 : CheckInput.GetDouble(fields[3], -1))); + } else if (n > 0) { // first line is expected invalid: is title + logger.log(Level.WARNING, String.format("Invalid item on line %d in %s (%s)", (n + 1), toload.getName(), + fields[0] + ":" + (fields[1].equals(" ") ? "0" : fields[1]) + (fields.length <= 4 ? "" : " [" + fields[4] + "]"))); + } + } else { + logger.log(Level.WARNING, String.format("unexpected pricelist line at %d in %s", (n + 1), toload.getName())); + } + } + } finally { + in.close(); + } + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error opening " + toload.getName() + " for reading", ex); + } + logger.log(Level.SEVERE, "Error opening " + toload.getName() + " for reading", ex); + } finally { + if (fstream != null) { + try { + fstream.close(); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error closing " + toload.getName(), ex); + } + logger.log(Level.SEVERE, "Error closing " + toload.getName(), ex); + } + } + } + return isLoaded = true && save(); + } else { // !toload.exists() + // still set as loaded + isLoaded = true; + return save(); + } + } + return false; + } + + /** + * closes connections & frees up used memory + * @throws IOException + * @throws SQLException + */ + public void close() throws IOException, SQLException { + // no need to save.. is saved after every edit + //save(); + if (databaseType == DBType.MYSQL && MySQLpricelist != null) { + MySQLpricelist.commit(); + MySQLpricelist.disconnect(); + MySQLpricelist = null; + } + priceList.clear(); + lastCacheUpdate = null; + isLoaded = false; + } + + public boolean isLoaded() { + if (databaseType == DBType.MYSQL) { + return (dbCacheTTL == 0 && MySQLpricelist.isConnected()) || isLoaded; + } + return isLoaded; + } + + public boolean save() throws IOException, SQLException { + sort(); + if (databaseType == DBType.MYSQL) { + try { + MySQLpricelist.commit(); + return true; + } catch (SQLException ex) { + if (!log_nothrow) { + throw new SQLException("Error executing COMMIT", ex); + } + logger.log(Level.SEVERE, "Error executing COMMIT", ex); + return false; + } + } else { + try { + return saveFile(flatFile); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error Saving " + (flatFile == null ? "flatfile price database" : flatFile.getName()), ex); + } + logger.log(Level.SEVERE, "Error Saving " + (flatFile == null ? "flatfile price database" : flatFile.getName()), ex); + return false; + } + } + } + + public boolean saveFile(File tosave) throws IOException { + if (tosave != null && !tosave.isDirectory()) { + if (!tosave.exists()) { + File dir = new File(tosave.getAbsolutePath().substring(0, tosave.getAbsolutePath().lastIndexOf(File.separatorChar))); + dir.mkdirs(); + try { + if (!tosave.createNewFile()) { + return false; + } + } catch (Exception e) { + return false; + } + } + if (tosave.canWrite()) { + + FileWriter fstream = null; + try { + fstream = new FileWriter(tosave.getAbsolutePath()); + //System.out.println("writing to " + tosave.getAbsolutePath()); + BufferedWriter out = new BufferedWriter(fstream); + out.write("id,subdata,buyprice,sellprice,name"); + out.newLine(); + for (PriceListItem i : priceList) { + // names provided for others to easily edit db + //out.write(i.ID() + "," + i.Data() + "," + i.buy + "," + i.sell + "," + i.Name()); + out.write(String.format(Locale.US, "%d,%d,%.2f,%.2f,%s", i.ID(), i.Data(), i.buy, i.sell, i.Name())); + out.newLine(); + } + out.flush(); + out.close(); + } catch (IOException ex) { + if (!log_nothrow) { + throw new IOException("Error opening " + tosave.getName() + " for writing", ex); + } + logger.log(Level.SEVERE, "Error opening " + tosave.getName() + " for writing", ex); + } finally { + if (fstream != null) { + try { + fstream.close(); + } catch (IOException ex) {/* + if (!log_nothrow) { + throw new IOException("Error closing " + tosave.getName(), ex); + } + logger.log(Level.SEVERE, "Error closing " + tosave.getName(), ex);*/ + + } + } + } + return true; + } + } else if (tosave == null) { + throw new IOException("no file to save to"); + } else if (tosave.isDirectory()) { + throw new IOException("file to save is a directory"); + } + return false; + } + + public void updateCache() throws SQLException, Exception { + updateCache(true); + } + + public void updateCache(boolean checkFirst) throws SQLException, Exception { + if (databaseType == DBType.FLATFILE) { + // (flatfile is cached, manually updated) + return; + } + //System.out.println("use c:" + useCache + " " + dbCacheTTL); + if (!checkFirst || (useCache && lastCacheUpdate == null) + || (useCache && dbCacheTTL > 0 && lastCacheUpdate != null + && ((new Date()).getTime() - lastCacheUpdate.getTime()) / 1000 > dbCacheTTL)) { + //System.out.println("updating cache (" + (lastCacheUpdate != null?((new Date()).getTime() - lastCacheUpdate.getTime())/100:-1) + "s since last update)"); + // MySQL cache outdated: update + List update = MySQLpricelist.getFullList(); + priceList.clear(); + priceList.addAll(update); + lastCacheUpdate = new Date(); + } + //else cache up-to-date, or disabled + } + + public boolean itemExists(ItemStockEntry i) throws SQLException, Exception { + return itemExists(JItemDB.GetItem(i.itemNum, (byte) i.itemSub)); + } + + public boolean itemExists(String check) throws SQLException, Exception { + return itemExists(JItemDB.findItem(check)); + } + + public boolean itemExists(ItemStack check) throws SQLException, Exception { + return itemExists(JItemDB.findItem(check)); + } + + public boolean itemExists(JItem check) throws SQLException, Exception { + if (check == null) { + return false; + } + updateCache(true); + if (databaseType == DBType.MYSQL && dbCacheTTL == 0) { + tempCache = MySQLpricelist.getItem(check); + } else { // should be in cache + int i = priceList.indexOf(new PriceListItem(check)); + if (i < 0) { + return false; + }//else + if (tempCache == null) { + tempCache = new PriceListItem(priceList.get(i)); + } else { + tempCache.Set(priceList.get(i)); + } + } + return tempCache != null; + } + + public boolean isForSale(String s) throws SQLException, Exception { + if (tempCache != null && tempCache.Name().equals(s)) { + // has been retrieved recently + return tempCache.sell >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(s) && tempCache.sell >= 0;//getSellPrice(s) >= 0; + } + + public boolean isForSale(JItem i) throws SQLException, Exception { + if (tempCache != null && tempCache.equals(i)) { + // has been retrieved recently + return tempCache.sell >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.sell >= 0;//getSellPrice(i) >= 0; + } + + public boolean isForSale(ItemStack i) throws SQLException, Exception { + if (tempCache != null && tempCache.equals(i)) { + // has been retrieved recently + return tempCache.sell >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.sell >= 0;//getSellPrice(i) >= 0; + } + + public boolean isForSale(ItemStockEntry i) throws SQLException, Exception { + if (tempCache != null && tempCache.ID() == i.itemNum && tempCache.Data() == i.itemSub) { + // has been retrieved recently + return tempCache.sell >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.sell >= 0;//getSellPrice(i) >= 0; + } + + public boolean canBuy(String s) throws SQLException, Exception{ + if (tempCache != null && tempCache.Name().equals(s)) { + // has been retrieved recently + return tempCache.buy >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(s) && tempCache.buy >= 0; + } + + public boolean canBuy(JItem i) throws SQLException, Exception { + if (tempCache != null && tempCache.equals(i)) { + // has been retrieved recently + return tempCache.buy >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.buy >= 0; + } + + public boolean canBuy(ItemStack i) throws SQLException, Exception { + if (tempCache != null && tempCache.equals(i)) { + // has been retrieved recently + return tempCache.buy >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.buy >= 0; + } + + public boolean canBuy(ItemStockEntry i) throws SQLException, Exception { + if (tempCache != null && tempCache.ID() == i.itemNum && tempCache.Data() == i.itemSub) { + // has been retrieved recently + return tempCache.buy >= 0; + } + //if itemExists, tempCache will contain the item + return itemExists(i) && tempCache.buy >= 0; + } + + public double getSellPrice(ItemStack i) throws SQLException, Exception { + return getSellPrice(JItemDB.findItem(i)); + } + + public double getSellPrice(String s) throws SQLException, Exception { + return getSellPrice(JItemDB.findItem(s)); + } + + public double getSellPrice(ItemStockEntry it) throws SQLException, Exception { + return getSellPrice(JItemDB.GetItem(it.itemNum, (byte) it.itemSub)); + } + + public double getSellPrice(JItem it) throws SQLException, Exception { + if (it == null) { + return -1; + } + PriceListItem itp = getItemPrice(it); + return itp != null ? itp.sell : -1; + + } + + public double getSellPrice(int id, byte dat) throws SQLException, Exception { + PriceListItem itp = getItemPrice(id, dat); + return itp != null ? itp.sell : -1; + } + + public double getBuyPrice(String s) throws SQLException, Exception { + return getBuyPrice(JItemDB.findItem(s)); + } + + public double getBuyPrice(JItem it) throws SQLException, Exception { + if (it == null) { + return -1; + } + PriceListItem itp = getItemPrice(it); + return itp != null ? itp.buy : -1; + } + + public double getBuyPrice(int id, byte dat) throws SQLException, Exception { + PriceListItem itp = getItemPrice(id, dat); + return itp != null ? itp.buy : -1; + } + + public PriceListItem getItemPrice(JItem it) throws SQLException, Exception { + return it == null ? null : getItemPrice(it.ID(), (byte) it.Data()); + } + + public PriceListItem getItemPrice(int id, byte dat) throws SQLException, Exception { + if (tempCache != null && tempCache.ID() == id && tempCache.Data() == dat + && (System.currentTimeMillis() - tempCache.getTime()) / 1000 < tempCacheTTL) { + // use temp + return tempCache; + } + if (databaseType == DBType.MYSQL && !useCache) { + tempCache = MySQLpricelist.getItem(id, dat); + } else { + updateCache(true); + PriceListItem f = null; + for (PriceListItem p : priceList) { + if (p != null && p.ID() == id && p.Data() == dat) { + f = p; + break; + } + } + if (f == null) { + return null; + } + if (tempCache == null) { + tempCache = new PriceListItem(f); + } else { + tempCache.Set(f); + } + } + return tempCache; + } + + public boolean setPrice(String item, double b, double s) throws SQLException, IOException, Exception { + return setPrice(JItemDB.findItem(item), b, s); + } + + public boolean setPrice(JItem it, double b, double s) throws SQLException, IOException, Exception { + if (it == null) { + return false; + } + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLpricelist.setPrice(it, b, s); + updateCache(false); + return true; + } else { + int i = priceList.indexOf(new PriceListItem(it)); + if (i < 0) { + priceList.add(new PriceListItem(it, b, s)); + } else { + priceList.get(i).SetPrice(b, s); + } + return save(); + } + } + + public boolean remove(String s) throws SQLException, Exception { + return remove(JItemDB.findItem(s)); + } + + public boolean remove(JItem it) throws SQLException, Exception { + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLpricelist.removeItem(it); + updateCache(false); + return true; + } else { + int i = priceList.indexOf(new PriceListItem(it)); + if (i >= 0 && priceList.remove(i) != null) {//priceList.remove(new PriceListItem(it))) { + return save(); + } + } + return false; + } + + public void removeAll() throws IOException, SQLException { + tempCache = null; + if (databaseType == DBType.MYSQL) { + MySQLpricelist.removeAll(); + } else { + priceList.clear(); + save(); + } + } + + public List getItemList(boolean showIllegal) throws SQLException, Exception { + LinkedList items = new LinkedList(); + + for (int i = 0; i < priceList.size(); ++i) { + if ((!showIllegal && !priceList.get(i).IsLegal()) + || (priceList.get(i).buy < 0 && priceList.get(i).sell < 0)) { + continue; + } + items.add(priceList.get(i).coloredName()); + } + return items; + } + + public List getItemList(boolean showIllegal, String itemTail) throws SQLException, Exception { + LinkedList items = new LinkedList(); + + for (int i = 0; i < priceList.size(); ++i) { + if (!showIllegal && !priceList.get(i).IsLegal() + || (priceList.get(i).buy < 0 && priceList.get(i).sell < 0)) { + continue; + } + items.add(priceList.get(i).coloredName() + itemTail); + } + return items; + } + + /** + * size of items that shop will buy or sell + * @return + */ + public int getShopSize() { + int num = priceList.size(); + for (PriceListItem i : priceList) { + if (i.buy < 0 && i.sell < 0) { + --num; + } + } + return num; + } + + /** + * number of items that shop will buy or sell & is legal or can buy illegal + * @param showIllegal + * @return + */ + public int getShopSize(boolean showIllegal) { + int num = priceList.size(); + for (PriceListItem i : priceList) { + if ((i.buy < 0 && i.sell < 0) + || !(showIllegal || i.IsLegal())) { + --num; + } + } + return num; + } + + /** + * + * @param pageNum zero-indexed page to get + * @param pageSize size of an output page + * @param showIllegal whether to count illegal items + * @return + */ + public int getShopPageStart(int pageNum, int pageSize, boolean showIllegal) { + if (pageSize <= 0) { + pageSize = 1; + } + int num = 0, showNum = 0; + for (PriceListItem i : priceList) { + if (!((i.buy < 0 && i.sell < 0) + || !(showIllegal || i.IsLegal()))) { + if (showNum / pageSize == pageNum) { + break; + } + ++showNum; + } + ++num; + } + return num; + } + + /** + * returns a page of prices + * @param pageNum page to lookup (-1 will print all pages) + * @param isPlayer if should use minecraft font spacing + * @param pageSize how many on a page + * @param listing format to output listing with + * @param header page header ( of ) + * @param footer page footer + * @return a list of formatted lines + * @throws SQLException if using MySQL database & there was some database connection error + * @throws Exception some serious error occurred (details in message) + */ + public List getShopListPage(int pageNum, boolean isPlayer, int pageSize, String listing, String header, String footer) throws SQLException, Exception { + return getShopListPage(pageNum, isPlayer, pageSize, listing, header, footer, false, true); + } + + /** + * returns a page of prices + * @param pageNum page to lookup (-1 will print all pages) + * @param isPlayer if should use minecraft font spacing + * @param pageSize how many on a page + * @param listing format to output listing with + * @param header page header ( of ) + * @param footer page footer + * @param showIllegal whether illegal items should be included in the listing + * @param showDec whether to round to whole numbers or show 2 decimal places + * @return a list of formatted lines + * @throws SQLException if using MySQL database & there was some database connection error + * @throws Exception some serious error occurred (details in message) + */ + public List getShopListPage(int pageNum, boolean isPlayer, int pageSize, String listing, String header, String footer, boolean showIllegal, boolean showDec) throws SQLException, Exception { + return getShopListPage(pageNum, isPlayer, pageSize, listing, header, footer, showIllegal, showDec, null); + } + + /** + * returns a page of prices + * @param pageNum page to lookup (-1 will print all pages) + * @param isPlayer if should use minecraft font spacing + * @param pageSize how many on a page + * @param listing format to output listing with + * @param header page header ( of ) + * @param footer page footer + * @param showIllegal whether illegal items should be included in the listing + * @param showDec whether to round to whole numbers or show 2 decimal places + * @param stock what to use for stock, if applicable + * @return a list of formatted lines + * @throws SQLException if using MySQL database & there was some database connection error + * @throws Exception some serious error occurred (details in message) + */ + public List getShopListPage(int pageNum, boolean isPlayer, + int pageSize, String listing, String header, String footer, + boolean showIllegal, boolean showDec, ItemStock stock) throws SQLException, Exception { + return getShopListPage(pageNum, isPlayer, pageSize, listing, + header, footer, showIllegal, showDec, stock, 0); + } + + /** + * returns a page of prices + * @param pageNum page to lookup (-1 will print all pages) + * @param isPlayer if should use minecraft font spacing + * @param pageSize how many on a page + * @param listing format to output listing with + * @param header page header ( of ) + * @param footer page footer + * @param showIllegal whether illegal items should be included in the listing + * @param showDec whether to round to whole numbers or show 2 decimal places + * @param stock what to use for stock, if applicable + * @param discount percentage to mark the price down + * @return a list of formatted lines + * @throws SQLException if using MySQL database & there was some database connection error + * @throws Exception some serious error occurred (details in message) + */ + public List getShopListPage(int pageNum, boolean isPlayer, + int pageSize, String listing, String header, String footer, + boolean showIllegal, boolean showDec, ItemStock stock, double discount) throws SQLException, Exception { + LinkedList ret = new LinkedList(); + if (databaseType == DBType.MYSQL && !useCache) { + updateCache(false);// manually update + } else { + updateCache(); + } + + int pricelistsize = getShopSize(showIllegal);//priceList.size(); + + int pages = (int) Math.ceil((double) pricelistsize / pageSize); + + int pageStart; + if (pageNum <= 0) { + //pageNum = 1; + pageStart = 0; + pageSize = pricelistsize; + } else { + pageStart = getShopPageStart(pageNum - 1, pageSize, showIllegal); + } + + String listhead = header == null || header.length() == 0 ? "" + : header.replace("", pageNum < 0 ? "(All)" : String.valueOf(pageNum)). + replace("", String.valueOf(pages)); + if (pageNum > pages) { + ret.add("There is no page " + pageNum + ". (" + pages + " pages total)"); + } else { + if (listhead.length() > 0) { + ret.add(String.format(listhead, pageNum, pages)); + } + listing = listing.replace("", "%1$s").replace("", "%2$s").replace("", "%3$s").replace("", "%4$s"); + ///for (int i = pageSize * (pageNum - 1), n = 0; n < pageSize && i < priceList.size(); ++i, ++n) { + for (int i = pageStart, n = 0; n < pageSize && i < priceList.size(); ++i, ++n) { + PriceListItem it = priceList.get(i); + if ((!showIllegal && !it.IsLegal()) + || (it.buy < 0 && it.sell < 0)) { + --n; + continue; + } + long st = stock != null ? stock.getItemAmount(it) : -1; + double buy = it.buy, + sell = it.sell; + if (discount != 0) { + buy -= buy * discount; + sell -= sell * discount; + } + ret.add(String.format(listing, it.coloredName(), + String.format("%5s", buy < 0 ? " No " : (showDec ? String.format("%01.2f", buy) : String.valueOf((int) Math.round(buy)))), + String.format("%5s", sell < 0 ? " No " : (showDec ? String.format("%01.2f", sell) : String.valueOf((int) Math.round(sell)))), + (st < 0 ? "INF" : String.valueOf(st)))); + } + if (footer != null && footer.length() > 0) { + ret.add(footer); + } + } + if (ret.size() > 2) { + // format spaces + return MinecraftChatStr.alignTags(ret, isPlayer); + } + return ret; + } + + public JItem[] getItems() throws SQLException, Exception { + return (JItem[]) getPricelistItems(); + } + + public JItem[] getItems(boolean showIllegal) throws SQLException, Exception { + //for(JItem i :((JItem[])getPricelistItems())) System.out.println(i); + if (showIllegal) { + return getPricelistItems(); + } + //else + return (JItem[]) getPricelistItems(showIllegal); + } + + public PriceListItem[] getPricelistItems() throws SQLException, Exception { + if (databaseType == DBType.MYSQL && !useCache) { + updateCache(false);// manually update + } else { + updateCache(true); + } + return priceList.toArray(new PriceListItem[0]); + } + + public PriceListItem[] getPricelistItems(boolean showIllegal) throws SQLException, Exception { + if (databaseType == DBType.MYSQL && !useCache) { + // manually update + priceList.clear(); + priceList.addAll(MySQLpricelist.getFullList()); + } else { + updateCache(); + } + ArrayList items = new ArrayList(); + for (int i = 0; i < priceList.size(); ++i) { + if (!showIllegal && !priceList.get(i).IsLegal()) { + continue; + } + items.add(priceList.get(i)); + } + return items.toArray(new PriceListItem[0]); + } +} // end class PriceList + diff --git a/src/me/jascotty2/lib/bukkit/shop/ShopDiscount.java b/src/me/jascotty2/lib/bukkit/shop/ShopDiscount.java new file mode 100644 index 0000000..a863afb --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/ShopDiscount.java @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: static class for users/groups and purchase discounts + * .. this is not integrated into a MySQL database.. might add if requested + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.bukkit.item.JItemDB; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.LinkedList; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ShopDiscount { + + protected static final Logger logger = Logger.getLogger("Minecraft"); + protected static LinkedList discounts = new LinkedList(); + protected static boolean isLoaded = false; + public static String discountFilename = "discounts.csv"; + + public static boolean loadFile(String filename) { + discountFilename = filename; + return loadFile(); + } + + public static boolean loadFile() { + if ((new File(discountFilename)).exists()) { + FileReader fstream = null; + discounts.clear(); + try { + fstream = new FileReader(discountFilename); + BufferedReader in = new BufferedReader(fstream); + try { + int n = 0; + in.readLine();// first line is expected invalid: is title + for (String line = null; (line = in.readLine()) != null && line.length() > 0 && line.split(",").length > 3; ++n) { + String fields[] = line.split(","); + Discount d = new Discount(); + d.isUser = fields[0].equalsIgnoreCase("u"); + d.discount = CheckInput.GetDouble(fields[1], 0); + int i = 2; + for (; i < fields.length; ++i) { + if (fields[i].length() > 0) { + if (fields[i].equalsIgnoreCase("!")) { + break; + } + // add as allowed user + d.addUser(fields[i]); + } + } + for (; i < fields.length; ++i) { + if (fields[i].length() > 0) // add as allowed item + { + d.addItem(JItemDB.findItem(fields[i])); + } + } + } + } finally { + in.close(); + } + } catch (IOException ex) { + logger.log(Level.WARNING, "Error opening " + discountFilename + " for reading", ex); + } finally { + try { + fstream.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error closing " + discountFilename, ex); + } + } + } + return isLoaded = true; + } + + public boolean saveFile() { + File tosave = new File(discountFilename); + if (tosave != null && !tosave.isDirectory()) { + if (!tosave.exists() || tosave.canWrite()) { + FileWriter fstream = null; + try { + fstream = new FileWriter(tosave.getAbsolutePath()); + BufferedWriter out = new BufferedWriter(fstream); + out.write("type,discount,users/groups,items,! denotes start of item list"); + out.newLine(); + for (Discount i : discounts) { + out.write((i.isUser ? "u," : "g,") + i.discount + "," + i.getUserListStr() + ",!," + i.getItemListStr()); + out.newLine(); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error opening " + discountFilename + " for writing", ex); + } finally { + try { + fstream.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error closing " + discountFilename, ex); + } + } + } + } + return false; + } +} // end class ShopDiscount + diff --git a/src/me/jascotty2/lib/bukkit/shop/TotalTransaction.java b/src/me/jascotty2/lib/bukkit/shop/TotalTransaction.java new file mode 100644 index 0000000..f100b11 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/TotalTransaction.java @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for logging total shop item transactions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import java.util.Date; +import org.bukkit.inventory.ItemStack; + +/** + * @author jacob + */ +public class TotalTransaction { + + public int itemNum, itemSub; + public String name; + public long sold, bought; + public long time; + + public TotalTransaction() { + name = "null"; + time = (new Date()).getTime() / 1000; + } // end default constructor + + public TotalTransaction(UserTransaction init) { + time = (new Date()).getTime() / 1000; + + itemNum = init.itemNum; + itemSub = init.itemSub; + name = init.name; + if (init.sold) { + sold = init.amount; + } else { + bought = init.amount; + } + } + + public TotalTransaction(JItem item, long numSold, long numBought) { + time = (new Date()).getTime() / 1000; + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = numSold; + bought = numBought; + } + + public TotalTransaction(long time, int num, int sub, String itemname, long numSold, long numBought) { + this.time = time; + itemNum = num; + itemSub = sub; + name = itemname; + sold = numSold; + bought = numBought; + } + + public TotalTransaction(long time, JItem item, long numSold, long numBought) { + this.time = time; + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = numSold; + bought = numBought; + } + + public JItem GetItem() { + return JItemDB.GetItem(itemNum, (byte) itemSub); + } + + @Override + public String toString() { + return String.format("%d:%d, '%s', +%d, -%d, %d", itemNum, itemSub, name, bought, sold, time); + } + + // only checks item, user, & if sold, not amount + public boolean equals(UserTransaction t) { + return itemNum == t.itemNum && itemSub == t.itemSub; + } + + public boolean equals(JItem it) { + return itemNum == it.ID() && itemSub == it.Data(); + } + + public boolean equals(ItemStack it) { + return itemNum == it.getTypeId() && itemSub == it.getDurability(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof UserTransaction) { + return equals((UserTransaction) o); + } + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + this.itemNum; + hash = 53 * hash + this.itemSub; + hash = 53 * hash + (int) (this.sold ^ (this.sold >>> 32)); + hash = 53 * hash + (int) (this.bought ^ (this.bought >>> 32)); + return hash; + } +} // end class TotalTransaction + diff --git a/src/me/jascotty2/lib/bukkit/shop/TransactionLog.java b/src/me/jascotty2/lib/bukkit/shop/TransactionLog.java new file mode 100644 index 0000000..9841cd1 --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/TransactionLog.java @@ -0,0 +1,396 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: for keeping a rolling history of shop transactions + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.io.FileIO; +import me.jascotty2.lib.io.CheckInput; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.mysql.MySQL; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.jascotty2.lib.util.Str; + +/** + * @author jacob + */ +public class TransactionLog { + + protected MySQL MySQLconnection = null; + protected boolean isLoaded = false; + // file, if flatfile + protected File flatFile = null, totalsFlatFile = null; + // cache of records + protected ArrayList transactions = new ArrayList(); + protected ArrayList totalTransactions = new ArrayList(); + protected ArrayList recentTotalTransactions = new ArrayList(); + public boolean logUserTransactions = true, logTotalTransactions = false; + public String transLogTablename = "UserTransactionLog", + recordTablename = "TotalTransactionLog"; + public long userTansactionLifespan = 172800; + protected final static Logger logger = Logger.getLogger("Minecraft"); + + public TransactionLog() { + } // end default constructor + + public void addRecord(UserTransaction rec) throws SQLException, IOException, Exception { + + //System.out.println("add record: " + BetterShop.getConfig().logUserTransactions + " " + BetterShop.getConfig().logTotalTransactions); + if (logUserTransactions) { + truncateRecords(false); + transactions.add(rec); + + //System.out.println("adding " + rec + " (" + transactions.size()); + if (MySQLconnection != null) { + try { + MySQLconnection.runUpdate("INSERT INTO `" + transLogTablename + + String.format(Locale.US, "` VALUES(UNIX_TIMESTAMP(), '%s', %d, %d, '%s', %d, %d, %.2f);", + Str.strTrim(rec.user, 50), rec.itemNum, rec.itemSub, Str.strTrim(rec.name, 25), rec.amount, rec.sold ? 1 : 0, rec.price)); + } catch (SQLException e) { + throw new SQLException("Error inserting transaction data", e); + } + } else { + // append to transaction list & save + // if going to update Total Transactions as well, postpone save + if (!logTotalTransactions) { + save(); + } + } + } + if (logTotalTransactions) { + + boolean exst = false; + for (TotalTransaction t : totalTransactions) { + if (t.itemNum == rec.itemNum && t.itemSub == rec.itemSub) { + exst = true; + if (rec.sold) { + t.sold += rec.amount; + } else { + t.bought += rec.amount; + } + break; + } + } + if (!exst) { + totalTransactions.add(new TotalTransaction(rec)); + } + + if (MySQLconnection != null) {// && MySQLconnection.isConnected() + try { + if (MySQLconnection.getQuery( + String.format("SELECT * FROM `%s` WHERE ID='%d' AND SUB='%d';", + recordTablename, rec.itemNum, rec.itemSub)).first()) { + // exists: update + if (rec.sold) { + MySQLconnection.runUpdate( + String.format("UPDATE `%s` SET SOLD = SOLD + %d, LAST=UNIX_TIMESTAMP() WHERE ID='%d' AND SUB='%d';", + recordTablename, rec.amount, rec.itemNum, rec.itemSub)); + } else { + MySQLconnection.runUpdate( + String.format("UPDATE `%s` SET BOUGHT = BOUGHT + %d, LAST=UNIX_TIMESTAMP() WHERE ID='%d' AND SUB='%d';", + recordTablename, rec.amount, rec.itemNum, rec.itemSub)); + } + } else { + MySQLconnection.runUpdate( + String.format("INSERT INTO `%s` VALUES(%d, %d, '%s', %d, %d, UNIX_TIMESTAMP());", + recordTablename, rec.itemNum, rec.itemSub, Str.strTrim(rec.name, 25), + rec.sold ? rec.amount : 0, rec.sold ? 0 : rec.amount)); + } + } catch (SQLException ex) { + throw new SQLException("Error running MySQL Query/Update/Insert while updating transaction totals", ex); + } + } else { + save(); + } + } + } + + /** + * removes records older than specified interval + * @throws SQLException + * @throws IOException + * @throws Exception + */ + public void truncateRecords() throws SQLException, IOException, Exception { + truncateRecords(true); + } + + /** + * + * @param saveFile if using flatfile, whether should save on completion + * @throws SQLException + * @throws IOException + * @throws Exception + */ + public void truncateRecords(boolean saveFile) throws SQLException, IOException, Exception { + if (MySQLconnection != null) { + try { + //BetterShopLogger.Log("DELETE FROM " + BetterShop.getConfig().sql_database + "." + BetterShop.getConfig().transLogTablename + " WHERE UNIX_TIMESTAMP() - DATE > " + BetterShop.getConfig().userTansactionLifespan + ";"); + MySQLconnection.runUpdate("DELETE FROM `" + transLogTablename + + "` WHERE UNIX_TIMESTAMP() - DATE > " + userTansactionLifespan + ";"); + } catch (SQLException e) { + throw new SQLException("Error while removing old records", e); + } + updateCache(); + } else { + // loop through cache & remove old entries + long curTime = (new Date()).getTime(); + for (int i = transactions.size() - 1; i > 0; --i) { + if (transactions.get(i).time - curTime > userTansactionLifespan) { + transactions.remove(i); + } + } + if (saveFile) { + save(); + } + } + + if (logUserTransactions) { + recentTotalTransactions.clear(); + // now update user totals + // (for MySQL, could use SELECT MAX(DATE) 'DATE', SOLD, SUM(AMT) 'AMT', NAME, ID, SUB, AVG(PRICE) 'PRICE' FROM $mysql_table GROUP BY ID, SUB, SOLD ORDER BY ID, SUB, SOLD + for (UserTransaction t : transactions) { + int pos = recentTotalTransactions.indexOf(t); + if (pos >= 0) { + if (t.sold) { + recentTotalTransactions.get(pos).sold += t.amount; + } else { + recentTotalTransactions.get(pos).bought += t.amount; + } + } else { + recentTotalTransactions.add(new TotalTransaction(t)); + } + } + } + } + + protected void updateCache() throws SQLException, IOException, Exception { + if (MySQLconnection != null) { + if (logUserTransactions) { + transactions.clear(); + recentTotalTransactions.clear(); + if (MySQLconnection.isConnected()) { + try { + ResultSet table = MySQLconnection.getQuery( + "SELECT * FROM `" + transLogTablename + "` ORDER BY DATE ASC;"); + + for (table.beforeFirst(); table.next();) { + transactions.add(new UserTransaction(table.getInt(1), table.getString(2), + table.getInt(3), table.getInt(4), table.getString(5), + table.getInt(6), table.getByte(7) != 0, table.getDouble(8))); + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + transLogTablename, ex); + } + } else { + throw new Exception("Error: MySQL DB not connected"); + } + } + // totals shouldn't need to be updated + if (logTotalTransactions) { + totalTransactions.clear(); + ResultSet tb = MySQLconnection.getTable(recordTablename); + for (tb.beforeFirst(); tb.next();) { + totalTransactions.add(new TotalTransaction( + tb.getLong("LAST"), tb.getInt("ID"), tb.getInt("SUB"), + tb.getString("NAME"), tb.getLong("SOLD"), tb.getLong("BOUGHT"))); + } + } + } else { + if (logUserTransactions) { + truncateRecords(); + transactions.clear(); + recentTotalTransactions.clear(); + if (flatFile != null && flatFile.exists()) { + try { + List actFile = FileIO.loadCSVFile(flatFile); + for (int n = 0; n < actFile.size(); ++n) {//String[] line : actFile) { + String[] line = actFile.get(n); + if (line.length >= 8) { + JItem plItem = JItemDB.findItem(line[2] + ":" + (line[3].equals(" ") ? "0" : line[3])); + if (plItem != null) { + //priceList.add(new PriceListItem(plItem, fields[2].length() == 0 ? -1 : CheckInput.GetDouble(fields[2], -1), fields[3].length() == 0 ? -1 : CheckInput.GetDouble(fields[3], -1))); + transactions.add(new UserTransaction(CheckInput.GetInt(line[0], 0), line[1], plItem, + CheckInput.GetInt(line[5], 0), CheckInput.GetInt(line[6], 0) != 0, CheckInput.GetDouble(line[7], 0))); + } else if (n > 0) { // first line is expected invalid: is title + logger.log(Level.WARNING, String.format("Invalid item on line %d in %s", (n + 1), flatFile.getName())); + } + } else { + logger.log(Level.WARNING, String.format("unexpected transaction line at %d in %s", (n + 1), flatFile.getName())); + } + } + } catch (FileNotFoundException ex) { + logger.log(Level.SEVERE, "Unexpected Error: File not found: " + flatFile.getName(), ex); + } catch (IOException ex) { + throw new IOException("Error opening " + flatFile.getName() + " for reading", ex); + } + //System.out.println("loaded " + transactions.size()); + // if also loading totals, postpone save + if (!logTotalTransactions) { + save(); + } + } + } + if (logTotalTransactions) { + if (totalsFlatFile != null && totalsFlatFile.exists()) { + totalTransactions.clear(); + try { + List actFile = FileIO.loadCSVFile(totalsFlatFile); + for (int n = 0; n < actFile.size(); ++n) {//String[] line : actFile) { + String[] line = actFile.get(n); + if (line.length >= 6) { + JItem plItem = JItemDB.findItem(line[1] + ":" + (line[2].equals(" ") ? "0" : line[2])); + if (plItem != null) { + //priceList.add(new PriceListItem(plItem, fields[2].length() == 0 ? -1 : CheckInput.GetDouble(fields[2], -1), fields[3].length() == 0 ? -1 : CheckInput.GetDouble(fields[3], -1))); + totalTransactions.add(new TotalTransaction(CheckInput.GetLong(line[0], 0), plItem, + CheckInput.GetLong(line[4], 0), CheckInput.GetLong(line[5], 0))); + } else if (n > 0) { // first line is expected invalid: is title + logger.log(Level.WARNING, String.format("Invalid item on line %d in %s", (n + 1), totalsFlatFile.getName())); + } + } else { + logger.log(Level.WARNING, String.format("unexpected pricelist line at %d in %s", (n + 1), totalsFlatFile.getName())); + } + } + } catch (FileNotFoundException ex) { + logger.log(Level.SEVERE, "Unexpected Error: File not found: " + totalsFlatFile.getName(), ex); + } catch (IOException ex) { + throw new IOException("Error opening " + totalsFlatFile.getName() + " for reading", ex); + } + //System.out.println("loaded " + totalTransactions.size()); + } else { + save(); + } + } + } + } + + protected final boolean createTransactionLogTable() throws SQLException { + if (!MySQLconnection.isConnected()) { + return false; + } + try { + MySQLconnection.runUpdate("CREATE TABLE `" + transLogTablename + + "`(DATE INTEGER UNSIGNED NOT NULL," // DEFAULT UNIX_TIMESTAMP() + + "USER VARCHAR(50) NOT NULL," + + "ID INTEGER NOT NULL," + + "SUB INTEGER NOT NULL," + + "NAME VARCHAR(25) NOT NULL," + + "AMT INTEGER NOT NULL," + + "SOLD TINYINT NOT NULL," + + "PRICE DECIMAL(11,2)," + + "PRIMARY KEY (DATE, USER, ID, SUB));"); + } catch (SQLException e) { + throw new SQLException("Error while creating transaction log table", e); + } + return true; + } + + protected final boolean createTransactionRecordTable() throws SQLException { + if (!MySQLconnection.isConnected()) { + return false; + } + try { + MySQLconnection.runUpdate("CREATE TABLE " + recordTablename + + "(ID INTEGER NOT NULL," + + "SUB INTEGER NOT NULL," + + "NAME VARCHAR(25) NOT NULL," + + "SOLD INTEGER NOT NULL," + + "BOUGHT INTEGER NOT NULL," + + "LAST INTEGER UNSIGNED NOT NULL," + + "PRIMARY KEY (ID, SUB));"); + } catch (SQLException e) { + throw new SQLException("Error while creating transaction log table", e); + } + return true; + } + + public boolean save() throws SQLException, IOException, Exception { + if (MySQLconnection != null) { + if (MySQLconnection.isConnected()) { + try { + MySQLconnection.commit(); + return true; + } catch (SQLException ex) { + throw new SQLException("Error executing COMMIT", ex); + } + } + } else { + if (logUserTransactions) { + if (flatFile != null){// && !flatFile.isDirectory()) { + ArrayList lines = new ArrayList(); + lines.add("date,user,id,sub,name,amt,sold,price"); + for (UserTransaction i : transactions) { + lines.add(i.time + "," + i.user + "," + + i.itemNum + "," + i.itemSub + "," + i.name + "," + + i.amount + "," + (i.sold ? "1" : "0") + "," + i.price); + } + try { + if (!FileIO.saveFile(flatFile, lines)) { + throw new Exception("Error writing to " + flatFile.getName()); + } + } catch (IOException ex) { + // todo: wait until try totalsFlatFile before throwing? + throw new IOException("Error opening " + flatFile.getName() + " for writing", ex); + } + + } else { + logger.log(Level.SEVERE, "Error saving activity log: undefined"); + } + } + + if (logTotalTransactions) { + if (totalsFlatFile != null) { + ArrayList lines = new ArrayList(); + lines.add("date,id,sub,name,sold,bought"); + for (TotalTransaction i : totalTransactions) { + lines.add(i.time + "," + + i.itemNum + "," + i.itemSub + "," + i.name + "," + + i.sold + "," + i.bought); + } + try { + if (!FileIO.saveFile(totalsFlatFile, lines)) { + throw new Exception("Error writing to " + totalsFlatFile.getName()); + } + } catch (IOException ex) { + throw new IOException("Error opening " + totalsFlatFile.getName() + " for writing", ex); + } + + } else { + logger.log(Level.SEVERE, "Error saving totals log: undefined"); + } + } + } + return false; + } + + public TotalTransaction recentItemTotals(JItem search) { + int pos = recentTotalTransactions.indexOf(search); + + return pos >= 0 ? recentTotalTransactions.get(pos) : null; + } +} // end class TransactionLog + diff --git a/src/me/jascotty2/lib/bukkit/shop/UserTransaction.java b/src/me/jascotty2/lib/bukkit/shop/UserTransaction.java new file mode 100644 index 0000000..ef9c4da --- /dev/null +++ b/src/me/jascotty2/lib/bukkit/shop/UserTransaction.java @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: an individual record of a user's transaction + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.bukkit.shop; + +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import java.util.Date; +import org.bukkit.inventory.ItemStack; + +public class UserTransaction { + + public int itemNum, itemSub, amount; + public short damage = 0; // if is a tool, can be edited by another method + public String name, user; + public boolean sold; + public double price; + public long time; + + public UserTransaction() { + name = user = "null"; + time = (new Date()).getTime() / 1000; + } // end default constructor + + public UserTransaction(int num, int sub, String name, boolean isSold, int amount, double price, String username) { + itemNum = num; + itemSub = sub; + this.name = name; + sold = isSold; + user = username; + this.amount = amount; + this.price = price; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(JItem item, boolean isSold, int amount, double price, String username) { + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = isSold; + user = username; + this.amount = amount; + this.price = price; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(JItem item, boolean isSold) { + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = isSold; + user = ""; + this.amount = 1; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(JItem item, boolean isSold, int amount) { + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = isSold; + user = ""; + this.amount = amount; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(ItemStack item, boolean isSold, int amount) { + itemNum = item.getTypeId(); + itemSub = item.getDurability(); + name = JItemDB.GetItemName(item); + sold = isSold; + user = ""; + this.amount = amount; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(ItemStack item, boolean isSold, int amount, double price, String username) { + itemNum = item.getTypeId(); + itemSub = item.getDurability(); + name = JItemDB.GetItemName(item); + sold = isSold; + user = username; + this.amount = amount; + this.price = price; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(ItemStockEntry item, boolean isSold, double price, String username) { + itemNum = item.itemNum; + itemSub = item.itemSub; + this.name = item.name; + sold = isSold; + user = username; + this.amount = (int) item.amount; + this.price = price; + time = (new Date()).getTime() / 1000; + } + + public UserTransaction(long time, String username, int num, int sub, String itemname, int amt, boolean isSold, double unitprice) { + this.time = time; + itemNum = num; + itemSub = sub; + name = itemname; + sold = isSold; + user = username; + amount = amt; + price = unitprice; + } + + public UserTransaction(long time, String username, JItem item, int amt, boolean isSold, double unitprice) { + this.time = time; + itemNum = item.ID(); + itemSub = item.Data(); + name = item.Name(); + sold = isSold; + user = username; + amount = amt; + price = unitprice; + } + + public JItem GetItem() { + return JItemDB.GetItem(itemNum, (byte) itemSub); + } + + @Override + public String toString() { + return String.format("'%s', %d, %d, '%s', %d, %d", user, itemNum, itemSub, name, amount, sold ? 1 : 0); + } + + // only checks item, user, & if sold, not amount + public boolean equals(UserTransaction t) { + return itemNum == t.itemNum && itemSub == t.itemSub && sold == t.sold + && (user == null || user.equalsIgnoreCase(t.user)); + } + + public boolean equals(TotalTransaction t) { + return itemNum == t.itemNum && itemSub == t.itemSub; + } + + public boolean equals(JItem it) { + return itemNum == it.ID() && itemSub == it.Data(); + } + + public boolean equals(ItemStack it) { + return it != null && itemNum == it.getTypeId() && itemSub == it.getDurability(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof UserTransaction) { + return equals((UserTransaction) o); + } else if (o instanceof TotalTransaction) { + return equals((TotalTransaction) o); + } + return false; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 47 * hash + this.itemNum; + hash = 47 * hash + this.itemSub; + hash = 47 * hash + (this.sold ? 1 : 0); + return hash; + } +} // end class UserTransaction + diff --git a/src/me/jascotty2/lib/io/CheckInput.java b/src/me/jascotty2/lib/io/CheckInput.java new file mode 100644 index 0000000..38a449f --- /dev/null +++ b/src/me/jascotty2/lib/io/CheckInput.java @@ -0,0 +1,266 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: provides checking for parsing numbers from strings & validating input + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.io; + +import java.math.BigInteger; +import java.util.regex.Pattern; + +/** + * @author jacob + */ +public class CheckInput { + + // double string checking from Double documentation + final static String Digits = "(\\p{Digit}+)"; + final static String HexDigits = "(\\p{XDigit}+)"; + // an exponent is 'e' or 'E' followed by an optionally + // signed decimal integer. + final static String Exp = "[eE][+-]?" + Digits; + final static String fpRegex = + ("[\\x00-\\x20]*" + // Optional leading "whitespace" + "[+-]?(" + // Optional sign character + "NaN|" + // "NaN" string + "Infinity|" + + // "Infinity" string + // A decimal floating-point string representing a finite positive + // number without a leading sign has at most five basic pieces: + // Digits . Digits ExponentPart FloatTypeSuffix + // + // Since this method allows integer-only strings as input + // in addition to strings of floating-point literals, the + // two sub-patterns below are simplifications of the grammar + // productions from the Java Language Specification, 2nd + // edition, section 3.10.2. + // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt + "(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp + ")?)|" + + // . Digits ExponentPart_opt FloatTypeSuffix_opt + "(\\.(" + Digits + ")(" + Exp + ")?)|" + + // Hexadecimal strings + "((" + + // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt + "(0[xX]" + HexDigits + "(\\.)?)|" + + // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt + "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" + + ")[pP][+-]?" + Digits + "))" + + "[fFdD]?))" + + "[\\x00-\\x20]*");// Optional trailing "whitespace" + // int + final static String IntPattern = "[+-]?" + Digits; // or: "^-?\\d+$" + + public static boolean IsInt(String input) { + //return Pattern.matches(IntPattern, input); + // pattern may be faster for small strings, but NumberFormatException is more accurate :( + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean IsLong(String input) { + //return Pattern.matches(IntPattern, input); + // pattern faster for small strings, but NumberFormatException is more accurate :( + try { + Long.parseLong(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean IsByte(String input) { + try { + Byte.parseByte(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean IsDouble(String input) { + return Pattern.matches(fpRegex, input); + } + + public static long GetLong(String input, long onError) { + if (input == null) { + return onError; + } + try { + return Pattern.matches(IntPattern, input) ? Long.parseLong(input) : onError; + } catch (NumberFormatException e) { + // just in case the number is too large... can never be too careful.. + return onError; + } + } + + public static int GetInt(String input, int onError) { + if (input == null) { + return onError; + } + try { + return Pattern.matches(IntPattern, input) ? Integer.parseInt(input) : onError; + } catch (NumberFormatException e) { + // just in case the number is too large... can never be too careful.. + return onError; + } + } + + public static short GetShort(String input, short onError) { + if (input == null) { + return onError; + } + try { + return Pattern.matches(IntPattern, input) ? Short.parseShort(input) : onError; + } catch (NumberFormatException e) { + // just in case the number is too large... can never be too careful.. + return onError; + } + } + + public static double GetDouble(String input, double onError) { + if (input == null) { + return onError; + } + try { + return Pattern.matches(fpRegex, input) ? Double.parseDouble(input) : onError; + } catch (NumberFormatException e) { + return onError; + } + } + + public static byte GetByte(String input, byte onError) { + if (input == null) { + return onError; + } + // not fully sure how to catch a byte with an expression, so checking int instead + try { + return Pattern.matches(IntPattern, input) ? Byte.parseByte(input) : onError; + } catch (NumberFormatException e) { + return onError; + } + } + + public static boolean GetBoolean(String input, boolean onError) { + if (input == null) { + return onError; + } + try { + return Boolean.parseBoolean(input); + } catch (NumberFormatException e) { + input.toLowerCase(); + if(IsInt(input)) { + return GetInt(input, 0) != 0; + } + if(input.equals("t") || input.equals("true")) return true; + if(input.equals("f") || input.equals("false")) return false; + return onError; + } + } + + public static BigInteger GetBigInt(String str, long defaultNum) { + if (str == null) { + return new BigInteger(String.valueOf(defaultNum)); + } + try { + return new BigInteger(str); + } catch (Exception e) { + return new BigInteger(String.valueOf(defaultNum)); + } + } + + public static BigInteger GetBigInt_TimeSpanInSec(String str) throws Exception { + return GetBigInt_TimeSpanInSec(str, 's'); + } + + public static BigInteger GetBigInt_TimeSpanInSec(String str, char defaultUnit) throws Exception { + BigInteger ret = new BigInteger("0"); + int charPos = 0; + for (; charPos < str.length(); ++charPos) { + if (!Character.isDigit(str.charAt(charPos))) { + break; + } + } + //boolean good = false; + if (charPos > 0) { + // double-check value + if (CheckInput.IsInt(str.substring(0, charPos))) { + ret = new BigInteger(str.substring(0, charPos)); + char unit = str.length() == charPos ? defaultUnit : str.charAt(charPos); + if (unit == 's') { + // do nothing: is already seconds + return ret; + } else if (unit == 'm') { + // it's annoying that this class doesn't accept a long.. + ret = ret.multiply(new BigInteger("60")); + } else if (unit == 'h') { + ret = ret.multiply(new BigInteger("3600")); + } else if (unit == 'd') { + ret = ret.multiply(new BigInteger("86400")); + } else if (unit == 'w') { + ret = ret.multiply(new BigInteger("604800")); + } else if (unit == 'M') { + // using 1m = 30 days + ret = ret.multiply(new BigInteger("18144000")); + } else { + throw new Exception("Unknown TimeSpan unit: " + str.charAt(charPos)); + } + return ret; + } else { + throw new Exception("Invalid Numerical Value: " + str); + } + } + // will throw it's own exception + return new BigInteger(str); + } + + /** + * attempts to extract a number from within a string
+ * ex. "$5.03", "5.03 Dollars"
+ * if there are multiple, will only return the first + * @param input + * @param onError + * @return + */ + public static double ExtractDouble(String input, double onError) { + int numStart = -1, numEnd = input.length() - 1; + boolean dec = false; + for (int i = 0; i < input.length(); ++i) { + if ((Character.isDigit(input.charAt(i)) + || (input.charAt(i) == '.' && !dec && (dec = true)))) { + if (numStart < 0) { + numStart = i; + } + } else if (numStart >= 0) { + numEnd = i - 1; + break; + } + } + if (numStart > 0 && input.charAt(numStart - 1) == '.') { + --numStart; + } + if (numStart > 0 && input.charAt(numStart - 1) == '-') { + --numStart; + } + if (numStart >= 0) { + return GetDouble(input.substring(numStart, numEnd + 1), onError); + } + return onError; + } +} // end class CheckInput + diff --git a/src/me/jascotty2/lib/io/ConsoleInput.java b/src/me/jascotty2/lib/io/ConsoleInput.java new file mode 100644 index 0000000..c955f81 --- /dev/null +++ b/src/me/jascotty2/lib/io/ConsoleInput.java @@ -0,0 +1,341 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: Wrapper Class for Scanner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.lib.io; + +import java.util.Scanner; +import java.util.regex.Pattern; + +public class ConsoleInput { + + protected Scanner kbin; + + public ConsoleInput() { + kbin = new Scanner(System.in); + } + + // hasNextLine can block if waiting for input + public void Clear() { + while (kbin.hasNextLine()) { + //System.out.println("clearing.. "); + kbin.nextLine(); + } + + } + + public double GetDouble(String prompt) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (!CheckInput.IsDouble(input)) { + System.out.println("Invalid Input"); + } else { + return Double.valueOf(input); + } + } + } + + public double GetDouble(String prompt, double min, double max) { + if(max= " + min); + } else if (ret > max) { + System.out.println("Input must be <= " + max); + } else { + return ret; + } + } + } + } + + public double GetDouble(String prompt, double min) { + String input; + double ret; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (!CheckInput.IsDouble(input)) { + System.out.println("Invalid Input"); + } else { + ret = Double.valueOf(input); + if (ret < min) { + System.out.println("Input must be >= " + min); + } else { + return ret; + } + } + } + } + + public int GetInt(String prompt) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim(); + if (!CheckInput.IsInt(input)) { + System.out.println("Invalid Input"); + } else { + return Integer.parseInt(input); + } + } + } + + public int GetInt(String prompt, int min, int max) { + if(max= " + min); + } else if (ret > max) { + System.out.println("Input must be <= " + max); + } else { + return ret; + } + } + } + } + + public int GetInt(String prompt, int min) { + String input; + int ret; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim(); + if (!CheckInput.IsInt(input)) { + System.out.println("Invalid Input"); + } else { + ret = Integer.parseInt(input); + if (ret < min) { + System.out.println("Input must be >= " + min); + } else { + return ret; + } + } + } + } + + public long GetLong(String prompt) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim(); + if (!CheckInput.IsLong(input)) { + System.out.println("Invalid Input"); + } else { + return Long.getLong(input); + } + } + } + + public long GetLong(String prompt, long min, long max) { + if (max < min) { + max = Integer.MAX_VALUE; + } + String input; + long ret; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim(); + if (!CheckInput.IsLong(input)) { + System.out.println("Invalid Input"); + } else { + ret = Long.parseLong(input); + if (ret < min) { + System.out.println("Input must be >= " + min); + } else if (ret > max) { + System.out.println("Input must be <= " + max); + } else { + return ret; + } + } + } + } + + public long GetLong(String prompt, long min) { + String input; + long ret; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim(); + if (!CheckInput.IsLong(input)) { + System.out.println("Invalid Input"); + } else { + ret = Long.parseLong(input); + if (ret < min) { + System.out.println("Input must be >= " + min); + } else { + return ret; + } + } + } + } + + public String GetString(String prompt) { + System.out.print(prompt); + return kbin.nextLine(); + } + + public String GetString(String prompt, int minlen, int maxlen) { + if(maxlen= " + minlen + " characters long"); + } else if (input.length() > maxlen) { + System.out.println("Input must be <= " + maxlen + " characters long"); + } else { + return input; + } + } + } + + public String GetString(String prompt, int minlen) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (input.length() < minlen) { + System.out.println("Input must be >= " + minlen + " characters long"); + } else { + return input; + } + } + } + + public String GetString(String prompt, String pattern) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (!Pattern.matches(pattern, input)) { + System.out.println("Input is in incorrect format"); + } else { + return input; + } + } + } + + public char GetChar(String prompt) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (input.length()==0) { + System.out.println("Input Cannot be Blank"); + } else { + return input.charAt(0); + } + } + } + + public char GetChar(String prompt, char defaultChar) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (input.length()==0) { + return defaultChar; + } else { + return input.charAt(0); + } + } + } + + public char GetChar(String prompt, char[] allowedChar) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (input.length()==0) { + System.out.println("Input Cannot be Blank"); + } else { + for(char c : allowedChar){ + if(Character.toLowerCase(input.charAt(0))==Character.toLowerCase(c)) return input.charAt(0); // c + } + System.out.println("Incorrect Entry"); + } + } + } + public char GetChar(String prompt, char defaultChar, char[] allowedChar) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine(); + if (input.length()==0) { + return defaultChar; + } else { + for(char c : allowedChar){ + if(Character.toLowerCase(input.charAt(0))==Character.toLowerCase(c)) return input.charAt(0); // c + } + System.out.println("Incorrect Entry"); + } + } + } + + public boolean GetBool(String prompt) { + String input; + //Clear(); + while (true) { + System.out.print(prompt); + input = kbin.nextLine().trim().toLowerCase(); + if(input.length()>0){ + if(input.charAt(0)=='y' || input.charAt(0)=='1') return true; + if(input.charAt(0)=='n' || input.charAt(0)=='0') return false; + // else + System.out.println("Input a 'y' or 'n'"); + } + } + } + +} // end class ConsoleInput + diff --git a/src/me/jascotty2/lib/io/FileIO.java b/src/me/jascotty2/lib/io/FileIO.java new file mode 100644 index 0000000..a9a6c73 --- /dev/null +++ b/src/me/jascotty2/lib/io/FileIO.java @@ -0,0 +1,161 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for reading/writing files + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.io; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author jacob + */ +public class FileIO { + + public static List loadCSVFile(File toLoad) throws FileNotFoundException, IOException { + ArrayList ret = new ArrayList(); + if (toLoad.exists() && toLoad.isFile() && toLoad.canRead()) { + FileReader fstream = null; + fstream = new FileReader(toLoad.getAbsolutePath()); + BufferedReader in = new BufferedReader(fstream); + try { + int n = 0; + for (String line = null; (line = in.readLine()) != null && line.length() > 0; ++n) { + // if was edited in openoffice, will instead have semicolins.. + ret.add(line.replace(";", ",").replace(",,", ", ,").split(",")); + } + } finally { + in.close(); + } + } + return ret; + } + + public static List loadFile(File toLoad) throws FileNotFoundException, IOException { + ArrayList ret = new ArrayList(); + if (toLoad.exists() && toLoad.isFile() && toLoad.canRead()) { + FileReader fstream = null; + fstream = new FileReader(toLoad.getAbsolutePath()); + BufferedReader in = new BufferedReader(fstream); + try { + int n = 0; + for (String line = null; (line = in.readLine()) != null && line.length() > 0; ++n) { + ret.add(line); + } + } finally { + in.close(); + } + } + return ret; + } + + public static boolean saveFile(File toSave, String[] lines) throws IOException { + if (!toSave.exists() && !toSave.createNewFile()) { + return false; + } + if (toSave.canWrite()) { + FileWriter fstream = null; + fstream = new FileWriter(toSave.getAbsolutePath()); + //System.out.println("writing to " + tosave.getAbsolutePath()); + BufferedWriter out = new BufferedWriter(fstream); + for (String line : lines) { + out.write(line); + out.newLine(); + } + out.flush(); + out.close(); + return true; + } + return false; + } + + public static boolean saveFile(File toSave, ArrayList lines) throws IOException { + if (!toSave.exists()) { + // TODO: first check if directory exists, then create the file + File dir = new File(toSave.getAbsolutePath().substring(0, toSave.getAbsolutePath().lastIndexOf(File.separatorChar))); + dir.mkdirs(); + try { + if (!toSave.createNewFile()) { + return false; + } + } catch (Exception e) { + return false; + } + } + if (toSave.canWrite()) { + FileWriter fstream = null; + fstream = new FileWriter(toSave.getAbsolutePath()); + //System.out.println("writing to " + tosave.getAbsolutePath()); + BufferedWriter out = new BufferedWriter(fstream); + for (String line : lines) { + out.write(line); + out.newLine(); + } + out.flush(); + out.close(); + return true; + } + return false; + } + + public static boolean saveCSVFile(File toSave, ArrayList lines) throws IOException { + if (!toSave.exists()) { + // TODO: first check if directory exists, then create the file + File dir = new File(toSave.getAbsolutePath().substring(0, toSave.getAbsolutePath().lastIndexOf(File.separatorChar))); + dir.mkdirs(); + try { + if (!toSave.createNewFile()) { + return false; + } + } catch (Exception e) { + return false; + } + } + if (toSave.canWrite()) { + FileWriter fstream = null; + fstream = new FileWriter(toSave.getAbsolutePath()); + //System.out.println("writing to " + tosave.getAbsolutePath()); + BufferedWriter out = new BufferedWriter(fstream); + for (String line[] : lines) { + for (int i = 0; i < line.length; ++i) { + out.write(line[i]); + if (i + 1 < line.length) { + out.write(","); + } + } + out.newLine(); + } + out.flush(); + out.close(); + return true; + } + return false; + } + + public static File getJarFile(Class jarClass) { + return new File(jarClass.getProtectionDomain().getCodeSource().getLocation().getPath(). + replace("%20", " ").replace("%25", "%")); + } + +} // end class CSV + diff --git a/src/me/jascotty2/lib/mysql/MySQL.java b/src/me/jascotty2/lib/mysql/MySQL.java new file mode 100644 index 0000000..7afad3c --- /dev/null +++ b/src/me/jascotty2/lib/mysql/MySQL.java @@ -0,0 +1,399 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: class for working with a MySQL server + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.mysql; + +import java.io.File; +//import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.Connection; +//import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +//import java.sql.Statement; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MySQL { + + // local copy of current connection info + protected String sql_username, sql_password, sql_database, sql_hostName = "localhost", sql_portNum = "3306"; + // DB connection + protected Connection DBconnection = null; + private static int checkedDep = 0; + + public MySQL(){ + } + + public MySQL(MySQL copy) { + DBconnection = copy.DBconnection; + this.sql_username = copy.sql_username; + this.sql_password = copy.sql_password; + this.sql_database = copy.sql_database; + this.sql_hostName = copy.sql_hostName; + this.sql_portNum = copy.sql_portNum; + } + + public MySQL(String database, String username, String password, String hostName, String portNum) throws Exception { + // connect to database (for now, jeaves connection open) + connect(database, username, password, hostName, portNum); + } + + public MySQL(String database, String username, String password, String hostName) throws Exception { + connect(database, username, password, hostName); + } + + public MySQL(String database, String username, String password) throws Exception { + connect(database, username, password); + } + + public MySQL(String database, String username) throws Exception { + connect(database, username, ""); + } + + /** + * Connect to a database + * @param database MySQL database to use + * @param username MySQL username to connect as + * @param password MySQL user password + * @param hostName host of server + * @param portNum port to use + * @return if new connection successful (false if there was no change) + * @throws Exception + */ + public final boolean connect(String database, String username, String password, String hostName, String portNum) throws Exception { + if (!(isConnected() && sql_database.equals(database) + && sql_username.equals(username) + && sql_password.equals(password) + && sql_hostName.equals(hostName) + && sql_portNum.equals(portNum))) { + + sql_database = database; + sql_username = username; + sql_password = password; + sql_hostName = hostName; + sql_portNum = portNum; + return connect(); + } + return false; + } + + /** + * Connect to a database + * @param database MySQL database to use + * @param username MySQL username to connect as + * @param password MySQL user password + * @param hostName host of server + * @return if new connection successful (false if there was no change) + * @throws Exception + */ + public final boolean connect(String database, String username, String password, String hostName) throws Exception { + if (!(isConnected() && sql_database.equals(database) + && sql_username.equals(username) + && sql_password.equals(password) + && sql_hostName.equals(hostName))) { + + sql_database = database; + sql_username = username; + sql_password = password; + sql_hostName = hostName; + sql_portNum = "3306"; + return connect(); + } + return false; + } + + /** + * Connect to a database on localhost + * @param database MySQL database to use + * @param username MySQL username to connect as + * @param password MySQL user password + * @return if new connection successful (false if there was no change) + * @throws Exception + */ + public final boolean connect(String database, String username, String password) throws Exception { + if (!(isConnected() && sql_database.equals(database) + && sql_username.equals(username) + && sql_password.equals(password))) { + + sql_database = database; + sql_username = username; + sql_password = password; + sql_hostName = "localhost"; + sql_portNum = "3306"; + return connect(); + } + return false; + } + + /** + * Connect to a database on localhost (blank password) + * @param database MySQL database to use + * @param username MySQL username to connect as + * @return if new connection successful (false if there was no change) + * @throws Exception + */ + public final boolean connect(String database, String username) throws Exception { + if (!(isConnected() && sql_database.equals(database) + && sql_username.equals(username))) { + + sql_database = database; + sql_username = username; + sql_password = ""; + sql_hostName = "localhost"; + sql_portNum = "3306"; + return connect(); + } + return false; + } + + /** + * Checks if mysql-connector-java-bin.jar exists + * @return + */ + public static boolean checkDependency() { + //int checked = 0; + if (checkedDep != 0) { + return checkedDep > 0; + } + // file used by iConomy + // 4 can try, name shared with iConomy mysql.. i don't check for all possibilities, though) + String names[] = new String[]{"lib/mysql.jar", + "lib/mysql-connector-java-bin.jar", + "lib/mysql-connector-java-5.1.14-bin.jar", + "lib/mysql-connector-java-5.1.15-bin.jar"}; + File f = null; + for (int i = 0; i < names.length; ++i) { + f = new File(names[i]); + if (f.exists()) { + checkedDep = 1; + return true; + } + } + // !f.exists() + + checkedDep = -1; + return false; + + } + + /** + * Connect/Reconnect using current info + * @return if can connect & connected + * @throws Exception + */ + public final boolean connect() throws Exception { + if (DBconnection == null) { + // double-check that mysql-bin.jar exists + if (!checkDependency()) { + return false; + } + + // connect to database + Class.forName("com.mysql.jdbc.Driver").newInstance(); + + if (isConnected()) { + disconnect(); + } + + DBconnection = DriverManager.getConnection( + String.format("jdbc:mysql://%s:%s/%s?create=true,autoReconnect=true", sql_hostName, sql_portNum, sql_database), + sql_username, sql_password); + + // or append "user=%s&password=%s", sql_username, sql_password); + // create=true: create database if not already exist + // autoReconnect=true: should fix errors that occur if the connection times out + } else { + if (DBconnection.isClosed()) { + DBconnection = DriverManager.getConnection( + String.format("jdbc:mysql://%s:%s/%s?create=true,autoReconnect=true", sql_hostName, sql_portNum, sql_database), + sql_username, sql_password); + } + } + return true; + } + + /** + * close the MySQL server connection + */ + public void disconnect() { // throws Exception + try { + if (DBconnection != null && !DBconnection.isClosed()) { + DBconnection.close(); + } + } catch (SQLException ex) { + Logger.getLogger(MySQL.class.getName()).log(Level.SEVERE, "Error closing MySQL connection", ex); + } + DBconnection = null; + } + + /** + * manually force database to save + */ + public void commit() throws SQLException { + if (isConnected()) { + DBconnection.createStatement().executeUpdate("COMMIT;"); + } + } + + public ResultSet getQuery(String qry) throws SQLException { + + if (isConnected()) { + try { + if(!qry.trim().endsWith(";")){ + qry+=";"; + } + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE NAME='%s';", sql_tableName, name)); + return DBconnection.createStatement().executeQuery(qry); + + } catch (SQLException ex) { + // if lost connection & successfully reconnected, try again + if (isConnected(false) && isConnected(true)) { + return DBconnection.createStatement().executeQuery(qry); + } + //disconnect(); + throw ex; + } + } else { + return null; + } + } + + public int runUpdate(String qry) throws SQLException { // + if (isConnected()) { + try { + if(!qry.trim().endsWith(";")){ + qry+=";"; + } + return DBconnection.prepareStatement(qry).executeUpdate(); + } catch (SQLException ex) { + // if lost connection & successfully reconnected, try again + if (isConnected(false) && isConnected(true)) { + return DBconnection.prepareStatement(qry).executeUpdate(); + } + //disconnect(); + throw ex; + } + } else { + return -1; + } + } + + public ResultSet getTable(String tablename) throws SQLException { + if (isConnected()) { + try { + // Statement to use to issue SQL queries + //Statement st = DBconnection.createStatement(); + //ResultSet table = st.executeQuery("SELECT * FROM " + sql_tableName + ";"); + //PreparedStatement st= + //ResultSet table = DBconnection.prepareStatement("SELECT * FROM `" + sql_tableName + "`;").executeQuery(); + return DBconnection.createStatement().executeQuery("SELECT * FROM `" + tablename + "`;"); + + } catch (SQLException ex) { + // if lost connection & successfully reconnected, try again + if (isConnected(false) && isConnected(true)) { + return DBconnection.createStatement().executeQuery("SELECT * FROM `" + tablename + "`;"); + } + //disconnect(); + throw ex; + } + } else { + return null; + } + } + + /** + * check if is currently connected to a server + * preforms a pre-check, and if not & connection info exists, will attempt reconnect + */ + public boolean isConnected() { + return isConnected(true); + } + + public boolean isConnected(boolean reconnect) { + try { + if (DBconnection != null && DBconnection.isClosed()) { + try { + connect(); + } catch (Exception ex) { + // should not reach here, since is only thrown if creating a new connection + // (while connecting to the mysql lib) + } + } + return DBconnection != null && !DBconnection.isClosed(); + } catch (SQLException ex) { + Logger.getLogger(MySQL.class.getName()).log(Level.SEVERE, "Error checking MySQL connection status", ex); + DBconnection = null; + return false; + } + } + + /** + * check if connected & a table exists in this database + * @param tableName table to look up + * @throws SQLException + */ + public boolean tableExists(String tableName) throws SQLException { + if (isConnected()) { + //try { + return DBconnection.getMetaData().getTables(null, null, tableName, null).next(); + //} catch (SQLException ex) { + // Logger.getLogger(MySQL.class.getName()).log(Level.SEVERE, "Error retrieving table list", ex); + //} + } + return false; + } + + public boolean columnExists(String tableName, String columnName) throws SQLException { + if (isConnected()) { + ResultSet t = DBconnection.getMetaData().getColumns(null, null, tableName, null);//.getTables(null, null, tableName, null);// + for (;t.next();) { + //for(int i=1; i<=7; ++i) + //System.out.println(t.getString(i)); + //System.out.println(); + if(t.getString(4).equals(columnName)) + return true; + //try { + //t.getRowId(columnName); + //t.findColumn(columnName); + //} catch (SQLException ex) { + // Logger.getAnonymousLogger().log(Level.WARNING, ex.getMessage(), ex); + // return false; + //} + } + } + return false; + } + + public String getUserName() { + return sql_username; + } + + public String getDatabaseName() { + return sql_database; + } + + public String getHostName() { + return sql_hostName; + } + + public String getPortNum() { + return sql_portNum; + } +} // end class BSMySQL + diff --git a/src/me/jascotty2/lib/mysql/MySQLItemStock.java b/src/me/jascotty2/lib/mysql/MySQLItemStock.java new file mode 100644 index 0000000..6656939 --- /dev/null +++ b/src/me/jascotty2/lib/mysql/MySQLItemStock.java @@ -0,0 +1,316 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: class for working with a MySQL server + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.mysql; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.ItemStockEntry; +import me.jascotty2.lib.bukkit.item.JItemDB; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import java.util.LinkedList; +import java.util.List; +import me.jascotty2.lib.util.Str; + +public class MySQLItemStock extends MySQL { + + // local copy of current connection info + private String sql_tableName = "ItemStock"; + + public MySQLItemStock(String database, String tableName, + String username, String password, String hostName, String portNum) throws SQLException, Exception { + connect(database, username, password, hostName, portNum); + sql_database = database; + sql_tableName = tableName; + // now check & create table + if (isConnected() && !tableExists(tableName)) { + createItemStockTable(tableName); + } + } // end default constructor + + public MySQLItemStock(MySQL database, String tableName) throws SQLException { + super(database); + sql_tableName = tableName; + // now check & create table + if (isConnected() && !tableExists(tableName)) { + createItemStockTable(tableName); + } + } + + public final boolean connect(String database, String tableName, String username, String password, String hostName, String portNum) throws SQLException, Exception { + try { + if (connect(database, username, password, hostName, portNum)) { + sql_tableName = tableName; + } + } catch (SQLException ex) { + throw new SQLException("Error connecting to MySQL database", ex); + } catch (Exception e) { + throw new Exception("Failed to start database connection", e); + } + // now check if table is there + boolean exst = false; + try { + exst = tableExists(tableName); + } catch (SQLException ex) { + throw new SQLException("Error while retrieving table list", ex); + } catch (Exception e) { + throw new Exception("unexpected database error", e); + } + if (!exst) { + // table does not exist, so create it + createItemStockTable(tableName); + } + return true; + } + + public ItemStockEntry getItem(String name) throws SQLException, Exception { + if (isConnected()) { + try { + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE NAME='%s';", BetterShop.getConfig().sql_tableName, name)); + ResultSet table = getQuery(String.format( + "SELECT * FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(name, 25))); + if (table.first()) { + ItemStockEntry ret = new ItemStockEntry(); + ret.itemNum = table.getInt(1); + ret.itemSub = table.getByte(2); + ret.name = table.getString(3); + ret.amount = table.getLong(4); + return ret; + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Is Not connected to database: not checking for item"); + } + return null; + } + + public ItemStockEntry getItem(JItem item) throws SQLException, Exception { + return getItem(item.ID(), (int) item.Data()); + } + + public ItemStockEntry getItem(int id, byte dat) throws SQLException, Exception { + return getItem(id, (int) dat); + } + + public ItemStockEntry getItem(int id, int dat) throws SQLException, Exception { + if (isConnected()) { + try { + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE ID='%d' AND SUB='%d';", BetterShop.getConfig().sql_tableName, (int)Math.floor(item), (int) Math.round((item - Math.floor(item)) * 100.))); + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, + id, dat)); + if (table.first()) { + ItemStockEntry ret = new ItemStockEntry(); + ret.itemNum = table.getInt(1); + ret.itemSub = table.getByte(2); + ret.name = table.getString(3); + ret.amount = table.getLong(4); + return ret; + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Is Not connected to database: not checking for item"); + } + return null; + } + + public boolean setAmount(String itemName, long amt) throws SQLException { + if (itemExists(itemName)) { + try { + runUpdate( + String.format("UPDATE `%s` SET AMT='%d' WHERE NAME='%s';", + sql_tableName, amt, Str.strTrim(itemName, 25))); + /* or: + String.format("UPDATE %s SET BUY=%f1.2, SELL=%f1.2 WHERE ID='%d' AND SUB='%d';", + buy, sell, itemInfo.getItemTypeId(), itemInfo.getData())).executeUpdate(); + */ + return true; + } catch (SQLException ex) { + throw new SQLException("Error executing UPDATE on " + sql_tableName, ex); + } + } else { + JItem toAdd = JItemDB.findItem(itemName); + if (toAdd != null) { + try { + runUpdate( + String.format("INSERT INTO `%s` VALUES(%d, %d, '%s', '%d');", sql_tableName, + toAdd.ID(), toAdd.Data(), Str.strTrim(toAdd.Name(), 25), amt)); + return true; + } catch (SQLException ex) { + throw new SQLException("Error executing INSERT on " + sql_tableName, ex); + } + } + } + return false; + } + + public void setAmount(JItem item, long amt) throws SQLException { // + if (item != null) { + setAmount(item.ID(), (byte) item.Data(), item.Name(), amt); + } + } + + public void setAmount(int id, byte dat, String name, long amt) throws SQLException { // + if (itemExists(id, dat)) { + try { + //logger.log(Level.INFO, String.format("UPDATE %s SET BUY=%1.2f, SELL=%1.2f WHERE ID='%d' AND SUB='%d';", sql_tableName, buy, sell, item.itemId, (int) item.itemData)); + runUpdate( + String.format("UPDATE `%s` SET AMT=%d WHERE ID='%d' AND SUB='%d';", sql_tableName, + amt, id, dat)); + //return true; + } catch (SQLException ex) { + throw new SQLException("Error executing UPDATE on " + sql_tableName, ex); + } + } else { + //JItem toAdd = JItem.findItem(item); + // assuming item is valid + try { + //logger.log(Level.INFO, String.format("INSERT INTO %s VALUES(%d, %d, '%s', %1.2f, %1.2f);", sql_tableName, item.itemId, (int)item.itemData, item.name, buy, sell) + runUpdate( + String.format("INSERT INTO `%s` VALUES(%d, %d, '%s', %d);", sql_tableName, + id, dat, Str.strTrim(name, 25), amt)); + //return true; + } catch (SQLException ex) { + throw new SQLException("Error executing INSERT on " + sql_tableName, ex); + } + } + } + + public boolean itemExists(String item) throws SQLException { + if (isConnected()) { + try { + //logger.log(Level.INFO, String.format("SELECT * FROM %s WHERE NAME='%s';", sql_tableName, item)); + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(item, 25))); + return table.first(); + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } + return false; + } + + public boolean itemExists(JItem item) throws SQLException { + return item == null ? false : itemExists(item.ID(), (byte) item.Data()); + } + + public boolean itemExists(int id, byte dat) throws SQLException { + if (isConnected()) { + try { + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE ID='%d' AND SUB='%d';", sql_tableName, (int) Math.floor(item), (int) Math.round((item - Math.floor(item))* 100))); + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, + id, (int) dat)); + //BetterShopLogger.Log(Level.INFO, table.first()?"true":"false"); + return table.first(); + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } + return false; + } + + public boolean removeItem(String item) throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(item, 25))); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + return false; + } + + public boolean removeItem(JItem item) throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, item.ID(), (int) item.Data())); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + return false; + } + + public boolean clearDB() throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s`;", sql_tableName)); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + return false; + } + + public List getFullList() throws SQLException, Exception { + LinkedList tableDat = new LinkedList(); + if (isConnected()) { + try { + // Statement to use to issue SQL queries + //Statement st = DBconnection.createStatement(); + //ResultSet table = st.executeQuery("SELECT * FROM " + sql_tableName + ";"); + //PreparedStatement st= + //ResultSet table = DBconnection.prepareStatement("SELECT * FROM " + sql_tableName + ";").executeQuery(); + ResultSet table = getQuery( + "SELECT * FROM `" + sql_tableName + "` ORDER BY ID, SUB;"); + //BetterShopLogger.Log(Level.INFO, "Table selected: "); + for (table.beforeFirst(); table.next();) { + //BetterShopLogger.Log(Level.INFO, table.getString(3));// + tableDat.add(new ItemStockEntry(table.getInt(1), table.getByte(2), + table.getString(3), table.getLong(4))); + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Error: MySQL DB not connected"); + } + return tableDat; + } + + private boolean createItemStockTable(String tableName) throws SQLException { + if (!isConnected() || tableName.contains(" ")) { + return false; + } + try { + runUpdate("CREATE TABLE `" + tableName + + "`(ID INTEGER NOT NULL," + + "SUB TINYINT NOT NULL," + + "NAME VARCHAR(25) NOT NULL," + + "AMT BIGINT NOT NULL," + + "PRIMARY KEY (ID, SUB));"); + } catch (SQLException e) { + throw new SQLException("Error while creating table", e); + } + return true; + } + + public String getTableName() { + return sql_tableName; + } +} // end class MySQLPriceList + diff --git a/src/me/jascotty2/lib/mysql/MySQLPriceList.java b/src/me/jascotty2/lib/mysql/MySQLPriceList.java new file mode 100644 index 0000000..60be110 --- /dev/null +++ b/src/me/jascotty2/lib/mysql/MySQLPriceList.java @@ -0,0 +1,277 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: class for working with a MySQL server + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.mysql; + +import me.jascotty2.lib.bukkit.item.JItem; +import me.jascotty2.lib.bukkit.item.JItemDB; +import me.jascotty2.lib.bukkit.item.PriceListItem; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import me.jascotty2.lib.util.Str; + +public class MySQLPriceList extends MySQL { + + // local copy of current connection info + private String sql_tableName = "PriceList"; + + public MySQLPriceList(String database, String tableName, + String username, String password, String hostName, String portNum) throws SQLException, Exception { + connect(database, tableName, username, password, hostName, portNum); + } // end default constructor + + public final boolean connect(String database, String tableName, + String username, String password, String hostName, String portNum) throws SQLException, Exception { + try { + if (connect(database, username, password, hostName, portNum)) { + sql_tableName = tableName; + } + } catch (SQLException ex) { + throw new SQLException("Error connecting to MySQL database", ex); + } catch (Exception e) { + throw new Exception("Failed to start database connection", e); + } + // now check if table is there + try { + if (!tableExists(tableName)) { + // table does not exist, so create it + createPricelistTable(tableName); + } + } catch (SQLException ex) { + throw new SQLException("Error while retrieving table list", ex); + } catch (Exception e) { + throw new Exception("unexpected database error", e); + } + return true; + } + + public PriceListItem getItem(String name) throws SQLException, Exception { + if (isConnected()) { + try { + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE NAME='%s';", BetterShop.getConfig().sql_tableName, name)); + ResultSet table = getQuery(String.format( + "SELECT * FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(name, 25))); + if (table.first()) { + return new PriceListItem( + table.getInt(1), table.getByte(2), table.getString(3), + table.getDouble(4), table.getDouble(5)); + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Is Not connected to database: not checking for item"); + } + return null; + } + + public PriceListItem getItem(JItem item) throws SQLException, Exception { + return item != null ? getItem(item.ID(), (byte) item.Data()) : null; + } + + public PriceListItem getItem(int id, byte dat) throws SQLException, Exception { + if (isConnected()) { + try { + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, + id, (int) dat)); + if (table.first()) { + return new PriceListItem( + table.getInt(1), table.getByte(2), table.getString(3), + table.getDouble(4), table.getDouble(5)); + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Is Not connected to database: not checking for item"); + } + return null; + } + + public boolean setPrice(String itemName, double buy, double sell) throws SQLException { + if (itemExists(itemName)) { + try { + runUpdate( + String.format(Locale.US, "UPDATE `%s` SET BUY='%1.2f', SELL='%1.2f' WHERE NAME='%s';", sql_tableName, + buy, sell, Str.strTrim(itemName, 25))); + /* or: + String.format("UPDATE %s SET BUY=%f1.2, SELL=%f1.2 WHERE ID='%d' AND SUB='%d';", + buy, sell, itemInfo.getItemTypeId(), itemInfo.getData())).executeUpdate(); + */ + return true; + } catch (SQLException ex) { + throw new SQLException("Error executing UPDATE on " + sql_tableName, ex); + } + } else { + JItem toAdd = JItemDB.findItem(itemName); + if (toAdd != null) { + try { + runUpdate( + String.format(Locale.US, "INSERT INTO `%s` VALUES(%d, %d, '%s', '%1.2f', '%1.2f');", sql_tableName, + toAdd.ID(), toAdd.Data(), Str.strTrim(toAdd.Name(), 25), buy, sell)); + return true; + } catch (SQLException ex) { + throw new SQLException("Error executing INSERT on " + sql_tableName, ex); + } + } + } + return false; + } + + public void setPrice(JItem item, double buy, double sell) throws SQLException { // + if (itemExists(item)) { + try { + //java.util.logging.Logger.getAnonymousLogger().info(String.format("UPDATE %s SET BUY=%1.2f, SELL=%1.2f WHERE ID='%d' AND SUB='%d';", sql_tableName, buy, sell, item.ID(), (int) item.Data())); + runUpdate( + String.format(Locale.US, "UPDATE `%s` SET BUY='%1.2f', SELL='%1.2f' WHERE ID='%d' AND SUB='%d';", sql_tableName, + buy, sell, item.ID(), (int) item.Data())); + } catch (SQLException ex) { + throw new SQLException("Error executing UPDATE on " + sql_tableName, ex); + } + } else { + // assuming item is valid + try { + //java.util.logging.Logger.getAnonymousLogger().info(String.format("INSERT INTO %s VALUES(%d, %d, '%s', '%1.2f', '%1.2f');", sql_tableName, item.ID(), (int) item.Data(), item.Name(), buy, sell)); + runUpdate( + String.format(Locale.US, "INSERT INTO `%s` VALUES(%d, %d, '%s', '%1.2f', '%1.2f');", sql_tableName, + item.ID(), (int) item.Data(), Str.strTrim(item.Name(), 25), buy, sell)); + } catch (SQLException ex) { + throw new SQLException("Error executing INSERT on " + sql_tableName, ex); + } + } + } + + public boolean itemExists(String item) throws SQLException { + if (isConnected()) { + try { + //logger.log(Level.INFO, String.format("SELECT * FROM %s WHERE NAME='%s';", sql_tableName, item)); + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(item, 25) )); + return table.first(); + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } + return false; + } + + public boolean itemExists(JItem item) throws SQLException { + if (isConnected()) { + try { + //BetterShopLogger.Log(Level.INFO, String.format("SELECT * FROM %s WHERE ID='%d' AND SUB='%d';", sql_tableName, (int) Math.floor(item), (int) Math.round((item - Math.floor(item))* 100))); + ResultSet table = getQuery( + String.format("SELECT * FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, + item.ID(), (int) item.Data())); + //BetterShopLogger.Log(Level.INFO, table.first()?"true":"false"); + return table.first(); + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } + return false; + } + + public boolean removeItem(String item) throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s` WHERE NAME='%s';", sql_tableName, Str.strTrim(item, 25))); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + return false; + } + + public boolean removeItem(JItem item) throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s` WHERE ID='%d' AND SUB='%d';", sql_tableName, item.ID(), (int) item.Data())); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + return false; + } + + public void removeAll() throws SQLException { + if (isConnected()) { + try { + runUpdate(String.format( + "DELETE FROM `%s`;", sql_tableName)); + } catch (SQLException ex) { + throw new SQLException("Error executing DELETE on " + sql_tableName, ex); + } + } + } + + public List getFullList() throws SQLException, Exception { + LinkedList tableDat = new LinkedList(); + if (isConnected()) { + try { + // Statement to use to issue SQL queries + //Statement st = DBconnection.createStatement(); + //ResultSet table = st.executeQuery("SELECT * FROM " + sql_tableName + ";"); + //PreparedStatement st= + //ResultSet table = DBconnection.prepareStatement("SELECT * FROM " + sql_tableName + ";").executeQuery(); + ResultSet table = getQuery( + "SELECT * FROM `" + sql_tableName + "` ORDER BY ID, SUB;"); + //BetterShopLogger.Log(Level.INFO, "Table selected: "); + for (table.beforeFirst(); table.next();) { + //BetterShopLogger.Log(Level.INFO, table.getString(3));// + tableDat.add(new PriceListItem(table.getInt(1), table.getByte(2), + table.getString(3), table.getDouble(4), table.getDouble(5))); + } + } catch (SQLException ex) { + throw new SQLException("Error executing SELECT on " + sql_tableName, ex); + } + } else { + throw new Exception("Error: MySQL DB not connected"); + } + return tableDat; + } + + private boolean createPricelistTable(String tableName) throws SQLException { + if (!isConnected() || tableName.contains(" ")) { + return false; + } + try { + runUpdate("CREATE TABLE `" + sql_database + "`.`" + tableName + + "`(ID INTEGER NOT NULL," + + "SUB TINYINT NOT NULL," + + "NAME VARCHAR(25) NOT NULL," + + "BUY DECIMAL(11,2)," + + "SELL DECIMAL(11,2)," + + "PRIMARY KEY (ID, SUB));"); + } catch (SQLException e) { + throw new SQLException("Error while creating table", e); + } + return true; + } + + public String getTableName() { + return sql_tableName; + } +} // end class MySQLPriceList + diff --git a/src/me/jascotty2/lib/util/ArrayManip.java b/src/me/jascotty2/lib/util/ArrayManip.java new file mode 100644 index 0000000..77cc3bf --- /dev/null +++ b/src/me/jascotty2/lib/util/ArrayManip.java @@ -0,0 +1,175 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods fro manipulating & outputting arrays + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.Arrays; + +/** + * @author jacob + */ +public class ArrayManip { + + public static void printArray(OutputStream out, int array[], int cols) throws IOException { + //try { + int maxLen = 1; + for (int i = 0; i < array.length; ++i) { + if (String.valueOf(array[i]).length() > maxLen) { + maxLen = String.valueOf(array[i]).length(); + } + } + + for (int i = 0; i < array.length; ++i) { + out.write(Str.padLeft(String.valueOf(array[i]), maxLen + 1).getBytes()); + if (i % cols == cols - 1) { + out.write("\n".getBytes()); + } + } + if ((array.length - 1) % cols != cols - 1) { + out.write("\n".getBytes()); + } + //} catch (IOException ex) { + // Logger.getAnonymousLogger().log(Level.SEVERE, ex.getMessage(), ex); + //} + } + + public static void printArray(OutputStream out, int array[]) throws IOException { + for (int i = 0; i < array.length; ++i) { + out.write(String.valueOf(array[i]).getBytes()); + } + } + + public static void selectionSort(int array[], boolean ascending) { + for (int i = 0; i < array.length - 1; ++i) { + int swapI = i; + for (int j = i + 1; j < array.length; ++j) { + if ((ascending && array[j] < array[swapI]) + || (!ascending && array[j] > array[swapI])) { + swapI = j; + } + } + if (swapI != i) { + int t = array[i]; + array[i] = array[swapI]; + array[swapI] = t; + } + } + } + + public static void quickSort(int array[], boolean ascending) { + Arrays.sort(array, 0, array.length); + if (!ascending) { + reverse(array); + } + } + + public static void reverse(int array[]) { + for (int i = array.length / 2; i >= 0; --i) { + int t = array[i]; + array[i] = array[array.length - i - 1]; + array[array.length - i - 1] = t; + } + } + + public static void swapElem(int array[], int a, int b) { + if (a > 0 && b > 0 && a < array.length && b < array.length) { + int t = array[a]; + array[a] = array[b]; + array[b] = t; + } + } + + public static int[] arrayConcat(int arr1[], int arr2[]) { + if (arr1 == null || arr1.length == 0) { + return arr2 == null ? new int[0] : arr2; + } else if (arr2 == null || arr2.length == 0) { + return arr1 == null ? new int[0] : arr1; + } + int i = 0; + int ret[] = new int[arr1.length + arr2.length]; + for (; i < arr1.length; ++i) { + ret[i] = arr1[i]; + } + for (int n = 0; n < arr2.length; ++i, ++n) { + ret[i] = arr2[n]; + } + return ret; + } + + @SuppressWarnings("unchecked") + public static T[] arrayConcat(T arr1[], T arr2[]) { + if (arr1 == null && arr2 == null){ + return null; + } else if(arr1 == null || arr1.length == 0){ + return arr2; + } else if(arr2 == null || arr2.length == 0){ + return arr1; + } +// Object ret[] = new Object[arr1.length + arr2.length]; +// System.arraycopy(arr1, 0, ret, 0, arr1.length); +// System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); +// return (T[]) ret; + T[] ret = (T[]) Array.newInstance(arr1.getClass().getComponentType(), arr1.length + arr2.length); + System.arraycopy(arr1, 0, ret, 0, arr1.length); + System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); + return ret; + } + + @SuppressWarnings("unchecked") + public static T[] arraySub(T arr[], int startIndex, int endIndex) { + if(arr == null){ + return null; + } else if(startIndex >= arr.length){ + throw new IllegalArgumentException("startIndex is greater than the array"); + } else if(endIndex > arr.length){ + throw new IllegalArgumentException("endIndex is greater than the array"); + } else if(startIndex < 0){ + throw new IllegalArgumentException("startIndex cannot be negative"); + } else if(endIndex < startIndex){ + throw new IllegalArgumentException("startIndex is greater than the endIndex"); + } + T[] ret = (T[]) Array.newInstance(arr.getClass().getComponentType(), endIndex - startIndex); + System.arraycopy(arr, startIndex, ret, 0, ret.length); + return ret; + } + + @SuppressWarnings("unchecked") + public synchronized static int indexOf(T array[], T search) { + if (array == null || array.length == 0) { + return -1; + } + + if (search == null) { + for (int i = 0; i < array.length; ++i) { + if (array[i] == null) { + return i; + } + } + } else { + for (int i = 0; i < array.length; ++i) { + if (array[i] != null && search.equals(array[i])) { + return i; + } + } + } + return -1; + } +} // end class ArrayManip + diff --git a/src/me/jascotty2/lib/util/CustomLogger.java b/src/me/jascotty2/lib/util/CustomLogger.java new file mode 100644 index 0000000..4fa5254 --- /dev/null +++ b/src/me/jascotty2/lib/util/CustomLogger.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: ( TODO ) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.lib.util; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class CustomLogger extends Logger { + + protected String name; + protected final static String logFormat = "[%s] %s"; + + public CustomLogger() { + super(null, null); + //_logger = Logger.getAnonymousLogger(); + } + public CustomLogger(String name) { + super(null, null); + this.name=name; + //_logger = Logger.getAnonymousLogger(); + } + public CustomLogger(String name, String loggerName) { + super(loggerName, null); + this.name=name; + //_logger = loggerOut; + } + + @Override + public synchronized void log(Level level, String message, Object param) { + super.log(level, message == null ? null : String.format(logFormat, name, message), param); + //_logger.log(level, message == null ? null : String.format(logFormat, message), param); + //Log(level, message, param, true); + } + + @Override + public synchronized void log(Level level, String message, Object[] params) { + super.log(level, message == null ? null : String.format(logFormat, name, message), params); + //_logger.log(level, message == null ? null : String.format(logFormat, message), params); + //Log(level, message, params, true); + } + + @Override + public synchronized void log(Level level, String message, Throwable thrown) { + super.log(level, message == null ? null : String.format(logFormat, name, message), thrown); + //_logger.log(level, message == null ? null : String.format(logFormat, message), thrown); + //Log(level, message, thrown, true); + } + + @Override + public synchronized void severe(String msg) { + //Log(Level.SEVERE, msg, null, true); + log(Level.SEVERE, msg, (Object) null); + } + + @Override + public synchronized void warning(String msg) { + //Log(Level.WARNING, msg, null, true); + log(Level.WARNING, msg, (Object) null); + } + + @Override + public synchronized void info(String msg) { + //Log(Level.INFO, msg, null, true); + log(Level.INFO, msg, (Object) null); + } + + @Override + public synchronized void fine(String msg) { + //Log(Level.FINE, msg, null, true); + log(Level.FINE, msg, (Object) null); + } + + @Override + public synchronized void finer(String msg) { + //Log(Level.FINER, msg, null, true); + log(Level.FINER, msg, (Object) null); + } + + @Override + public synchronized void finest(String msg) { + //Log(Level.FINEST, msg, null, true); + log(Level.FINEST, msg, (Object) null); + } + +} // end class CutomLogger diff --git a/src/me/jascotty2/lib/util/Rand.java b/src/me/jascotty2/lib/util/Rand.java new file mode 100644 index 0000000..5fc0382 --- /dev/null +++ b/src/me/jascotty2/lib/util/Rand.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: provides easy random values + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.util; + +import java.util.Date; +import java.util.Random; + +public class Rand { + + static final Random rand = new Random(); + static final char[] filenameChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); + protected static boolean isRand = false; + + public static String randFname() { + return randFname(10, 25); + } + + public static String randFname(int length) { + return randFname(length, length); + } + + public static String randFname(int minlength, int maxlength) { + StringBuilder ret = new StringBuilder(); + for (int i = RandomInt(minlength, maxlength); i > 0; --i) { + ret.append(filenameChars[RandomInt(0, filenameChars.length - 1)]); + } + return ret.toString(); + } + + public static int RandomInt(int min, int max) { + if (min == max) { + return min; + } else if (max < min) { + return RandomInt(max, min); + } else if (!isRand) { + rand.setSeed((new Date()).getTime()); + isRand = true; + } + return min + rand.nextInt(max - min + 1); + } + + public static double RandomDouble() { + if (!isRand) { + rand.setSeed((new Date()).getTime()); + isRand = true; + } + return rand.nextDouble(); + } + + public static double RandomDouble(double min, double max) { + if (!isRand) { + rand.setSeed((new Date()).getTime()); + isRand = true; + } + return min + rand.nextDouble() * (max - min); + } + + public static boolean RandomBoolean() { + if (!isRand) { + rand.setSeed((new Date()).getTime()); + isRand = true; + } + return rand.nextBoolean(); + } + + public static boolean RandomBoolean(double chance) { + if (chance >= 1) { + return true; + }else if (chance <= 0) { + return false; + }else if (!isRand) { + rand.setSeed((new Date()).getTime()); + isRand = true; + } + return rand.nextDouble() <= chance; + } +} // end class Rand + diff --git a/src/me/jascotty2/lib/util/Str.java b/src/me/jascotty2/lib/util/Str.java new file mode 100644 index 0000000..e4adf10 --- /dev/null +++ b/src/me/jascotty2/lib/util/Str.java @@ -0,0 +1,630 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for manipulating Strings + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package me.jascotty2.lib.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.NumberFormat; +import java.util.Collection; + +public class Str extends OutputStream { + + protected StringBuilder text = new StringBuilder(); + + @Override + public void write(int b) throws IOException { + text.append((char) b); + } + + // Static Methods + static NumberFormat nf = NumberFormat.getInstance(), + cnf = NumberFormat.getCurrencyInstance(); + public static String numFormat(long num){ + return nf.format(num); + } + + public static String numFormat(double num){ + return nf.format(num); + } + + public static String currencyFormat(long num){ + return cnf.format(num); + } + + public static String currencyFormat(double num){ + return cnf.format(num); + } + + public static String concatStr(String... str) { + return concatStr(str, 0, ""); + } + + public static String concatStr(String[]... str) { + StringBuilder ret = new StringBuilder(); + for (String[] s : str) { + ret.append(concatStr(s)); + } + return ret.toString(); + } + + public static String concatStr(String[] s, String sep) { + return concatStr(s, 0, sep); + } + + public static String concatStr(String[] s, int start) { + return concatStr(s, start, ""); + } + + public static String concatStr(String[] s, int start, String sep) { + StringBuilder ret = new StringBuilder(); + if (s != null) { + for (int i = start; i < s.length; ++i) { + ret.append(s[i]); + if (i + 1 < s.length) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static String concatStr(String[] s, int start, String sep, int length) { + StringBuilder ret = new StringBuilder(); + if (s != null) { + for (int i = start, j = 0; i < s.length && j < length; ++i, ++j) { + ret.append(s[i]); + if (i + 1 < s.length) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static String concatStr(Collection strSet) { + return concatStr(strSet, 0, ""); + } + + public static String concatStr(Collection strSet, String sep) { + return concatStr(strSet, 0, sep); + } + + public static String concatStr(Collection strSet, int start) { + return concatStr(strSet, start, ""); + } + + public static String concatStr(Collection strSet, int start, String sep) { + if (strSet == null || strSet.isEmpty() || start >= strSet.size()) { + return ""; + } + StringBuilder ret = new StringBuilder(); + int i = 0; + //for(int i=0; i start) { + //ret.append(o.toString()); + //ret.append(String.valueOf(o)); + ret.append(o == null ? "null" : o.toString()); + if (i < strSet.size()) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static String concatStr(Collection strSet, int start, String sep, int length) { + if (strSet == null || strSet.isEmpty() || start >= strSet.size() || length <= 0) { + return ""; + } + StringBuilder ret = new StringBuilder(); + int i = 0, l = 0; + //for(int i=0; i start) { + ret.append(o == null ? "null" : o.toString()); + if (++l > length) { + break; + } else if (i < strSet.size()) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static String concatStr(Object[] strSet) { + return concatStr(strSet, 0, ""); + } + + public static String concatStr(Object[] strSet, String sep) { + return concatStr(strSet, 0, sep); + } + + public static String concatStr(Object[] strSet, int start) { + return concatStr(strSet, start, ""); + } + + public static String concatStr(Object[] strSet, int start, String sep) { + if (strSet == null || start >= strSet.length) { + return ""; + } + StringBuilder ret = new StringBuilder(); + int i = 0; + //for(int i=0; i start) { + //ret.append(o.toString()); + //ret.append(String.valueOf(o)); + ret.append(o == null ? "null" : o.toString()); + if (i < strSet.length) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static String concatStr(Object[] strSet, int start, String sep, int length) { + if (strSet == null || start >= strSet.length || length <= 0) { + return ""; + } + StringBuilder ret = new StringBuilder(); + int i = 0, l = 0; + //for(int i=0; i start) { + ret.append(o == null ? "null" : o.toString()); + if (++l > length) { + break; + } else if (i < strSet.length) { + ret.append(sep); + } + } + } + return ret.toString(); + } + + public static boolean isIn(String input, String... check) { + input = input.trim(); + for (String c : check) { + if (input.equalsIgnoreCase(c.trim())) { + return true; + } + } + return false; + } + + public static boolean isIn(String input, String[]... check) { + input = input.trim(); + for (String[] c : check) { + for (String c2 : c) { + if (input.equalsIgnoreCase(c2.trim())) { + return true; + } + } + } + return false; + } + + public static boolean isIn(String input, String check) { + input = input.trim(); + for (String c : check.split(",")) { + if (input.equalsIgnoreCase(c.trim())) { + return true; + } + } + return false; + } + + public static boolean startIsIn(String input, String check) { + for (String c : check.split(",")) { + if (input.length() >= c.length()) { + if (input.substring(0, c.length()).equalsIgnoreCase(c)) { + return true; + } + } + } + return false; + } + + public static boolean startIsIn(String input, String[] check) { + for (String c : check) { + if (input.length() >= c.length()) { + if (input.substring(0, c.length()).equalsIgnoreCase(c)) { + return true; + } + } + } + return false; + } + + public static int count(String str, String find) { + int c = 0; + for (int i = 0; i < str.length() - find.length(); ++i) { + if (str.substring(i, i + find.length()).equals(find)) { + ++c; + } + } + return c; + } + + public static int count(String str, char find) { + int c = 0; + for (int i = 0; i < str.length(); ++i) { + if (str.charAt(i) == find) { + ++c; + } + } + return c; + } + + public static int countIgnoreCase(String str, String find) { + int c = 0; + for (int i = 0; i < str.length() - find.length(); ++i) { + if (str.substring(i, i + find.length()).equalsIgnoreCase(find)) { + ++c; + } + } + return c; + } + + public static int indexOf(String array[], String search) { + if (array != null && array.length > 0) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i].equals(search)) { + return i; + } + } + } + return -1; + } + + public static int indexOfIgnoreCase(String array[], String search) { + for (int i = array.length - 1; i >= 0; --i) { + if (array[i].equalsIgnoreCase(search)) { + return i; + } + } + return -1; + } + + public static String getStackStr(Exception err) { + if (err == null) {// || err.getCause() == null) { + return ""; + } + Str stackoutstream = new Str(); + PrintWriter stackstream = new PrintWriter(stackoutstream); + err.printStackTrace(stackstream); + stackstream.flush(); + stackstream.close(); + return stackoutstream.text.toString(); + } + + public static String getStackStr(Throwable err) { + if (err == null) { + return ""; + } + Str stackoutstream = new Str(); + PrintWriter stackstream = new PrintWriter(stackoutstream); + err.printStackTrace(stackstream); + stackstream.flush(); + stackstream.close(); + return stackoutstream.text.toString(); + } + + /** + * pads str on the right (space-padded) (left-align) + * @param str + * @param len + * @return + */ + public static String padRight(String str, int len) { + return padRight(str, len, ' '); + } + + /** + * pads str on the right with pad (left-align) + * @param str + * @param len + * @param pad + * @return + */ + public static String padRight(String str, int len, char pad) { + StringBuilder ret = new StringBuilder(str); + for (int i = str.length(); i < len; ++i) { + ret.append(pad); + } + return ret.toString(); + } + + /** + * pads str on the left (space-padded) (right-align) + * @param str + * @param len + * @return + */ + public static String padLeft(String str, int len) { + return repeat(' ', len - str.length()) + str; + } + + /** + * pads str on the left with pad (right-align) + * @param str + * @param len + * @param pad + * @return + */ + public static String padLeft(String str, int len, char pad) { + return repeat(pad, len - str.length()) + str; + } + + /** + * pads str on the left & right (space-padded) (center-align) + * @param str + * @param len + * @return + */ + public static String padCenter(String str, int len) { + len -= str.length(); + int prepad = len / 2; + return repeat(' ', prepad) + str + repeat(' ', len - prepad); + } + + /** + * pads str on the left & right with pad (center-align) + * @param str + * @param len + * @param pad + * @return + */ + public static String padCenter(String str, int len, char pad) { + len -= str.length(); + int prepad = len / 2; + return repeat(pad, prepad) + str + repeat(pad, len - prepad); + } + + public static String strWordWrap(String str, int width) { + return strWordWrap(str, width, 0, ' '); + } + + public static String strWordWrap(String str, int width, int tab) { + return strWordWrap(str, width, tab, ' '); + } + + public static String strWordWrap(String str, int width, int tab, char tabChar) { + StringBuilder ret = new StringBuilder(); + while (str.length() > 0) { + // find last char of first line + if (str.length() <= width) { + return (ret.length() > 0 ? ret + "\n" + Str.repeat(tabChar, tab) : "").concat(str); + } + String line1 = strTrim(str, width); + int lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos == 0) { + lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + } + //ret += strPadRightChat((ret.length() > 0 ? unformattedStrRepeat(tabChar, tab) : "") + str.substring(0, lastPos)); + ret.append(ret.length() > 0 ? "\n" + Str.repeat(tabChar, tab) : "").append(str.substring(0, lastPos)); + str = str.substring(lastPos + 1); + } + return ret.toString(); + } + + /** + * right-aligns paragraphs + * @param str + * @param width + * @param tab + * @param tabChar + * @return + */ + public static String strWordWrapRight(String str, int width, int tab, char tabChar) { + StringBuilder ret = new StringBuilder(); + while (str.length() > 0) { + // find last char of first line + if (str.length() <= width) { + return (ret.length() > 0 ? ret + "\n" : "").concat(Str.padLeft(str, width, tabChar)); + } + String line1 = strTrim(str, width); + int lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + while (lastPos > 0 && line1.charAt(lastPos) != ' ') { + --lastPos; + } + if (lastPos <= 0) { + lastPos = line1.length() - (ret.length() > 0 && line1.length() > tab + 1 ? tab + 1 : 1); + } + //ret += strPadLeftChat(str.substring(0, lastPos), tabChar); + ret.append(ret.length() > 0 ? "\n" : "").append(Str.padLeft(str.substring(0, lastPos), width, tabChar)); + str = str.substring(lastPos + 1); + } + return ret.toString(); + } + + public static String strWordWrapRight(String str, int width, int tab) { + return strWordWrapRight(str, width, tab, ' '); + } + + public static String repeat(char ch, int len) { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < len; ++i) { + ret.append(ch); + } + return ret.toString(); + } + + /** + * Returns a sequence str of the provided str count # of times + * @param str + * @param count + * @return + */ + public static String repeat(String str, int count) { + StringBuilder ret = new StringBuilder(); + for (int i = 0; i < count; ++i) { + ret.append(str); + } + return ret.toString(); + } + + public static String strTrim(String str, int length) { + if (str.length() > length) { + int width = length; + String ret = ""; + boolean lastCol = false; + for (char c : str.toCharArray()) { + if (c == '\u00A7') { + ret += c; + lastCol = true; + } else { + if (!lastCol) { + if (width - 1 >= 0) { + width -= 1; + ret += c; + } else { + return ret; + } + } else { + ret += c; + lastCol = false; + } + } + } + } + return str; + } + + public static String titleCase(String str) { + if (str == null) { + throw new IllegalArgumentException("Error: String cannot be null"); + } + StringBuilder ret = new StringBuilder(); + boolean st = true; + for (char c : str.toLowerCase().toCharArray()) { + if (st) { + ret.append(Character.toTitleCase(c)); + } else { + ret.append(c); + } + st = c == ' '; + } + return ret.toString(); + } + + /** + *

Find the Levenshtein distance between two Strings.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

This Method was written by Chas Emerick, and which avoids an OutOfMemoryError + * which can occur when some Java implementations are used with very large strings.
+ * This implementation of the Levenshtein distance algorithm + * is from http://www.merriampark.com/ldjava.htm

+ * + *
+	 * StringUtil.getLevenshteinDistance("","")               = 0
+	 * StringUtil.getLevenshteinDistance("","a")              = 1
+	 * StringUtil.getLevenshteinDistance("aaapppp", "")       = 7
+	 * StringUtil.getLevenshteinDistance("frog", "fog")       = 1
+	 * StringUtil.getLevenshteinDistance("fly", "ant")        = 3
+	 * StringUtil.getLevenshteinDistance("elephant", "hippo") = 7
+	 * StringUtil.getLevenshteinDistance("hippo", "elephant") = 7
+	 * StringUtil.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+	 * StringUtil.getLevenshteinDistance("hello", "hallo")    = 1
+	 * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input null + */ + public static int getLevenshteinDistance(String s, String t) { + if (s == null || t == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + + /* + The difference between this impl. and the previous is that, rather + than creating and retaining a matrix of size s.length()+1 by t.length()+1, + we maintain two single-dimensional arrays of length s.length()+1. The first, d, + is the 'current working' distance array that maintains the newest distance cost + counts as we iterate through the characters of String s. Each time we increment + the index of String t we are comparing, d is copied to p, the second int[]. Doing so + allows us to retain the previous cost counts as required by the algorithm (taking + the minimum of the cost count to the left, up one, and diagonally up and to the left + of the current cost count being calculated). (Note that the arrays aren't really + copied anymore, just switched...this is clearly much better than cloning an array + or doing a System.arraycopy() each time through the outer loop.) + + Effectively, the difference between the two implementations is this one does not + cause an out of memory condition when calculating the LD over two very large strings. + */ + + int n = s.length(); // length of s + int m = t.length(); // length of t + + if (n == 0) { + return m; + } else if (m == 0) { + return n; + } + + int p[] = new int[n + 1]; //'previous' cost array, horizontally + int d[] = new int[n + 1]; // cost array, horizontally + int _d[]; //placeholder to assist in swapping p and d + + // indexes into strings s and t + int i; // iterates through s + int j; // iterates through t + + char t_j; // jth character of t + + int cost; // cost + + for (i = 0; i <= n; i++) { + p[i] = i; + } + + for (j = 1; j <= m; j++) { + t_j = t.charAt(j - 1); + d[0] = j; + + for (i = 1; i <= n; i++) { + cost = s.charAt(i - 1) == t_j ? 0 : 1; + // minimum of cell to the left+1, to the top+1, diagonally left and up +cost + d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost); + } + + // copy current distance counts to 'previous row' distance counts + _d = p; + p = d; + d = _d; + } + + // our last action in the above loop was to switch d and p, so p now + // actually has the most recent cost counts + return p[n]; + } +} // end class Str + diff --git a/src/me/jascotty2/lib/util/UnZip.java b/src/me/jascotty2/lib/util/UnZip.java new file mode 100644 index 0000000..ffb682f --- /dev/null +++ b/src/me/jascotty2/lib/util/UnZip.java @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2011 Jacob Scott + * Description: methods for unzipping a zip archive, or files from one + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.jascotty2.lib.util; + +import java.io.*; +import java.util.*; +import java.util.zip.*; + +public class UnZip { + + public static boolean isZip(String filename){ + try { + ZipFile zipFile = new ZipFile(filename); + zipFile.close(); + return true; + } catch (IOException ex) { + } + return false; + } + + public static void copyInputStream(InputStream in, OutputStream out) + throws IOException { + byte[] buffer = new byte[1024]; + int len; + + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + + in.close(); + out.close(); + } + + public static boolean unzip(String file, String dest) { + if (file == null) { + return false; + } + if (dest == null) { + dest = ""; + } else if (dest.length() > 0 && dest.charAt(dest.length() - 1) != File.separatorChar) { + dest += File.separatorChar; + } + if (dest.length() > 0 && !(new File(dest)).exists()) { + (new File(dest)).mkdir(); + } + try { + // (based on) method described at http://www.devx.com/getHelpOn/10MinuteSolution/20447 + // by Daniel F. Savarese + ZipFile zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + if (entry.isDirectory()) { + // Assume directories are stored parents first then children. + //System.err.println("Extracting directory: " + entry.getName()); + (new File(dest + entry.getName())).mkdir(); + continue; + } + + //System.err.println("Extracting file: " + entry.getName()); + copyInputStream(zipFile.getInputStream(entry), + new BufferedOutputStream(new FileOutputStream(dest + entry.getName()))); + } + + zipFile.close(); + return true; + } catch (IOException ioe) { + System.err.println("UnZip Error:"); + ioe.printStackTrace(); + return false; + } + } + + public static boolean unzip(String file, String subPathToExtract, String dest) { + return unzip(file, new String[]{subPathToExtract}, dest); + } + + public static boolean unzip(String file, String subPathsToExtract[], String dest) { + if (file == null) { + return false; + } + if (dest == null) { + dest = ""; + } else if (dest.length() > 0 && dest.charAt(dest.length() - 1) != File.separatorChar) { + dest += File.separatorChar; + } + if (dest.length() > 0 && !(new File(dest)).exists()) { + (new File(dest)).mkdir(); + } + try { + boolean good = false; // TODO: parallel array & stop if array fills + ZipFile zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + for (String s : subPathsToExtract) { + if (entry.getName().equals(s)) { + String extractfilename = (new File(s)).getName(); + //System.out.println("saving " + extractfilename + " at " + dest + extractfilename); + copyInputStream(zipFile.getInputStream(entry), + new BufferedOutputStream(new FileOutputStream(dest + extractfilename))); + good = true; + } + } + } + zipFile.close(); + return good; + } catch (IOException ioe) { + System.err.println("UnZip Error:"); + ioe.printStackTrace(); + return false; + } + } +} diff --git a/src/org/mcstats/Metrics.java b/src/org/mcstats/Metrics.java new file mode 100644 index 0000000..e70b2cb --- /dev/null +++ b/src/org/mcstats/Metrics.java @@ -0,0 +1,632 @@ +/* + * Copyright 2011 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ + +package org.mcstats; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; + +/** + *

+ * The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. + *

+ *

+ * Public methods provided by this class: + *

+ * + * Graph createGraph(String name);
+ * void addCustomData(Metrics.Plotter plotter);
+ * void start();
+ *
+ */ +public class Metrics { + + /** + * The current revision number + */ + private final static int REVISION = 5; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + + /** + * The separator to use for custom data. This MUST NOT change unless you are hosting your own + * version of metrics and want to change it. + */ + private static final String CUSTOM_DATA_SEPARATOR = "~~"; + + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 10; + + /** + * The plugin this metrics submits for + */ + private final Plugin plugin; + + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + + /** + * The default graph, used for addCustomData when you don't want a specific graph + */ + private final Graph defaultGraph = new Graph("Default"); + + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + + /** + * The plugin configuration file + */ + private final File configurationFile; + + /** + * Unique server id + */ + private final String guid; + + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + + /** + * Id of the scheduled task + */ + private volatile int taskId = -1; + + public Metrics(final Plugin plugin) throws IOException { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + this.plugin = plugin; + + // load the config + configurationFile = getConfigFile(); + configuration = YamlConfiguration.loadConfiguration(configurationFile); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://mcstats.org").copyDefaults(true); + configuration.save(configurationFile); + } + + // Load the guid then + guid = configuration.getString("guid"); + } + + /** + * Construct and create a Graph that can be used to separate specific plotters to their own graphs + * on the metrics website. Plotters can be added to the graph object returned. + * + * @param name The name of the graph + * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given + */ + public Graph createGraph(final String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + + // Construct the graph object + final Graph graph = new Graph(name); + + // Now we can add our graph + graphs.add(graph); + + // and return back + return graph; + } + + /** + * Add a Graph object to Metrics that represents data for the plugin that should be sent to the backend + * + * @param graph The name of the graph + */ + public void addGraph(final Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + + graphs.add(graph); + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter The plotter to use to plot custom data + */ + public void addCustomData(final Plotter plotter) { + if (plotter == null) { + throw new IllegalArgumentException("Plotter cannot be null"); + } + + // Add the plotter to the graph o/ + defaultGraph.addPlotter(plotter); + + // Ensure the default graph is included in the submitted graphs + graphs.add(defaultGraph); + } + + /** + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send + * the initial data to the metrics backend, and then after that it will post in increments of + * PING_INTERVAL * 1200 ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (taskId >= 0) { + return true; + } + + // Begin hitting the server with glorious data + taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { + + private boolean firstPost = true; + + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && taskId > 0) { + plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + // Tell all plotters to stop gathering information. + for (Graph graph : graphs){ + graph.onOptOut(); + } + } + } + + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + }, 0, PING_INTERVAL * 1200); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized(optOutLock) { + try { + // Reload the metrics file + configuration.load(getConfigFile()); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } + return configuration.getBoolean("opt-out", false); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } + + // Enable Task, if it is not running + if (taskId < 0) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (taskId > 0) { + this.plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + } + + /** + * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use + // is to abuse the plugin object we already have + // plugin.getDataFolder() => base/plugins/PluginA/ + // pluginsFolder => base/plugins/ + // The base is not necessarily relative to the startup directory. + File pluginsFolder = plugin.getDataFolder().getParentFile(); + + // return => base/plugins/PluginMetrics/config.yml + return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(final boolean isPing) throws IOException { + // The plugin's description file containg all of the plugin data such as name, version, author, etc + final PluginDescriptionFile description = plugin.getDescription(); + + // Construct the post data + final StringBuilder data = new StringBuilder(); + data.append(encode("guid")).append('=').append(encode(guid)); + encodeDataPair(data, "version", description.getVersion()); + encodeDataPair(data, "server", Bukkit.getVersion()); + encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); + encodeDataPair(data, "revision", String.valueOf(REVISION)); + + // If we're pinging, append it + if (isPing) { + encodeDataPair(data, "ping", "true"); + } + + // Acquire a lock on the graphs, which lets us make the assumption we also lock everything + // inside of the graph (e.g plotters) + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + // The key name to send to the metrics server + // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top + // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME + final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); + + // The value to send, which for the foreseeable future is just the string + // value of plotter.getValue() + final String value = Integer.toString(plotter.getValue()); + + // Add it to the http post data :) + encodeDataPair(data, key, value); + } + } + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data.toString()); + writer.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + final String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response == null || response.startsWith("ERR")) { + throw new IOException(response); //Throw the exception + } else { + // Is this the first update this hour? + if (response.contains("OK This is your first update this hour")) { + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first + * key/value pair MUST be included manually, e.g:

+ * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer the stringbuilder to append the data pair onto + * @param key the key value + * @param value the value + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) + * If it does not comply to the above when submitted, it is rejected + */ + private final String name; + + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet(); + + private Graph(final String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return the Graph's name + */ + public String getName() { + return name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter the plotter to add to the graph + */ + public void addPlotter(final Plotter plotter) { + plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter the plotter to remove from the graph + */ + public void removePlotter(final Plotter plotter) { + plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return an unmodifiable {@link Set} of the plotter objects + */ + public Set getPlotters() { + return Collections.unmodifiableSet(plotters); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Graph)) { + return false; + } + + final Graph graph = (Graph) object; + return graph.name.equals(name); + } + + /** + * Called when the server owner decides to opt-out of Metrics while the server is running. + */ + protected void onOptOut() { + } + + } + + /** + * Interface used to collect custom data for a plugin + */ + public static abstract class Plotter { + + /** + * The plot's name + */ + private final String name; + + /** + * Construct a plotter with the default plot name + */ + public Plotter() { + this("Default"); + } + + /** + * Construct a plotter with a specific plot name + * + * @param name the name of the plotter to use, which will show up on the website + */ + public Plotter(final String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point. Since this function defers to an external function + * it may or may not return immediately thus cannot be guaranteed to be thread friendly or safe. + * This function can be called from any thread so care should be taken when accessing resources + * that need to be synchronized. + * + * @return the current value for the point to be plotted. + */ + public abstract int getValue(); + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return name; + } + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + final Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } + + } + +} \ No newline at end of file diff --git a/src/plugin.yml b/src/plugin.yml index 3b8341d..a800940 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -1,33 +1,167 @@ name: BetterShop -main: com.bukkit.jjfs85.BetterShop.BetterShop -version: 1.3 -website: http://github.com/jjfs85/BetterShop -author: jjfs85 +main: me.jascotty2.bettershop.BetterShop +version: 2.1.6.6 +website: http://github.com/BetterShop/BetterShop +author: jascotty2 +softdepend: [Spout, Vault] description: > - BetterShop is a command-based shop for Bukkit. It uses iConomy - for the economic backbone. + BetterShop is a global command-based shop for Bukkit. + Supports Permissions, iConomy, BOSEconomy, MultiCurrency, + EssensialsEco, Help, MinecraftIM, and Spout + the ulitmate goal of this plugin is to be the most expandable shop plugin avaliable + currently supports MySQL or csv databases, + can use stock, transaction logs, command logs, legal item restrictions, + colored item listing, customizable shop sorting, item categories, + customizable item names & aliases, customizable user messages, + automatic error reporting (if something goes wrong), + customizable kits for sale, update checking & auto updating, + region-based shop modes, sign shops, chest shops, spout gui shop menu, + and more (hopefully) to come! :) commands: + shop: + description: Command Alias / Admin Command + usage: / [check|list|buy|sell|add|remove|[re]load|help] + aliases: [bettershop,bshop] shopcheck: description: Check prices for an item usage: / [item] + aliases: [sc,scheck] + #permission: Bettershop.user.* shoplist: description: Lists prices for the shop usage: / + aliases: [sl,slist] + #permission: BetterShop.user.list + shoplistalias: + description: show tha accepted aliases for an item + usage: / [item] + aliases: [shopalias, salias, sa] + #permission: BetterShop.user.help + shoplistitems: + description: show listing of items in shop, without prices + usage: / + aliases: [shopitems, sitems, slistitems] + #permission: BetterShop.user.list shopbuy: description: Buy an item for the price in the shop usage: / [item] [amount] + aliases: [buy,sbuy] + #permission: BetterShop.user.buy + shopbuyall: + description: Buy all of an item that you can hold + usage: / [item] + aliases: [buyall,sbuyall] + #permission: BetterShop.user.buy + shopbuystack: + description: Buy a stack of an item for the price in the shop + usage: / [item] + aliases: [buystack,sbuystack,sbuys,buys] + #permission: BetterShop.user.buy shopsell: description: Sell an item for the price in the shop usage: / [item] + aliases: [sell,ssell] + #permission: BetterShop.user.sell + shopsellall: + description: Sell all of item from your inventory + usage: / [item] + aliases: [sellall] + #permission: BetterShop.user.sell + shopsellstack: + description: Sell a stack of an item to the shop + usage: / [item] + aliases: [sellstack,ssellstack,sells,ssells] + #permission: BetterShop.user.sell shopadd: description: Add an item to or update an item in the price list usage: / [item] [buy-price] [sell-price] + aliases: [sadd] + #permission: BetterShop.admin.add shopremove: description: Remove an item from the price list usage: / [item] + aliases: [sremove] + #permission: BetterShop.admin.remove shopload: - description: reload prices from PriceList.yml + description: reload prices from pricelist database usage: / + aliases: [sload] + #permission: BetterShop.admin.load shophelp: description: Lists available commands usage: / + aliases: [shelp] + #permission: BetterShop.user.help + shoplistkits: + description: Lists available kits + usage: / + aliases: [shopkits, skits, slistkits] + #permission: BetterShop.user.list + shopbuyagain: + description: repeat last purchase + usage: / + aliases: [sbuyagain, buyagain, sba] + #permission: BetterShop.user.buy + shopsellagain: + description: repeat last sale + usage: / + aliases: [ssellagain, sellagain, ssa] + #permission: BetterShop.user.sell +permissions: + BetterShop.user.*: + description: all user commands + default: true + children: + BetterShop.user.list: true + BetterShop.user.check: true + BetterShop.user.help: true + BetterShop.user.buy: true + BetterShop.user.sell: true + BetterShop.user.spout: true + BetterShop.user.chest: true + BetterShop.user.list: + description: look through shop listing of prices + BetterShop.user.check: + description: check the price of item(s) + BetterShop.user.help: + description: view ingame help menu + BetterShop.user.buy: + description: buy items from the shop + BetterShop.user.sell: + description: sell items to the shop + BetterShop.user.spout: + description: allow a user to use the spout gui menu + BetterShop.user.chest: + description: allow a user to use the a chest shop + BetterShop.admin.*: + description: all admin permissions + default: op + children: + BetterShop.admin.add: true + BetterShop.admin.remove: true + BetterShop.admin.load: true + BetterShop.admin.info: true + BetterShop.admin.illegal: true + BetterShop.admin.backup: true + BetterShop.admin.restock: true + BetterShop.admin.makesign: true + BetterShop.admin.chests: true + BetterShop.admin.add: + description: add/edit items to/in the shop + BetterShop.admin.remove: + description: remove items from the shop + BetterShop.admin.load: + description: reload configuration & pricelist + BetterShop.admin.info: + description: show shop stats + BetterShop.admin.illegal: + description: gives the ability to purchase 'illegal' items + BetterShop.admin.backup: + description: backing up and restoring the pricelist + BetterShop.admin.restock: + description: manually restock (if item stock is enabled) + BetterShop.admin.makesign: + description: ability to add/remove shop signs + BetterShop.admin.chests: + description: ability to add/remove chest shops + \ No newline at end of file