diff --git a/.gitignore b/.gitignore index 571e5fcd01..60d8f2bc2e 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,8 @@ build/ /server/forwarding.secret /configuration/config.yml /server/proxy/configuration/config.yml -/configuration/resourcepacks/skyblockpack +/store/ +/configuration/resourcepacks/skyblockpack/ /configuration/resourcepacks/ravengard store/.next store/node_modules diff --git a/commons/src/generated/java/net/swofty/commons/skyblock/item/ItemType.java b/commons/src/generated/java/net/swofty/commons/skyblock/item/ItemType.java index c1325988ce..3a59152791 100644 --- a/commons/src/generated/java/net/swofty/commons/skyblock/item/ItemType.java +++ b/commons/src/generated/java/net/swofty/commons/skyblock/item/ItemType.java @@ -120,6 +120,8 @@ public enum ItemType { APPLE(Material.APPLE, Rarity.COMMON), + ARACHNE_FRAGMENT(Material.PLAYER_HEAD, Rarity.RARE), + ARACK(Material.IRON_SWORD, Rarity.EPIC), ARCHER_DUNGEON_ABILITY_2(Material.BOW, Rarity.UNCOMMON), @@ -172,7 +174,7 @@ public enum ItemType { AWKWARD_POTION(Material.POTION, Rarity.COMMON), - AXE_OF_THE_SHREDDED(Material.DIAMOND_AXE, Rarity.LEGENDARY), + AXE_OF_THE_SHREDDED(Material.DIAMOND_SWORD, Rarity.LEGENDARY), AZALEA(Material.AZALEA, Rarity.COMMON), @@ -246,6 +248,8 @@ public enum ItemType { BAT_WAND(Material.PAPER, Rarity.LEGENDARY), + BEADY_EYES(Material.SPIDER_EYE, Rarity.UNCOMMON), + BEASTMASTER_CREST_COMMON(Material.PLAYER_HEAD, Rarity.COMMON), BEASTMASTER_CREST_EPIC(Material.PLAYER_HEAD, Rarity.EPIC), @@ -428,6 +432,8 @@ public enum ItemType { BOOKSHELF(Material.BOOKSHELF, Rarity.COMMON), + BOOKWORMS_FAVORITE_BOOK(Material.BOOK, Rarity.RARE), + BOOSTER_COOKIE(Material.COOKIE, Rarity.LEGENDARY), BOSS_SPIRIT_BOW(Material.BOW, Rarity.EPIC), @@ -534,6 +540,8 @@ public enum ItemType { BURNING_KUUDRA_CORE(Material.PLAYER_HEAD, Rarity.RARE), + BURROWING_SPORES(Material.BROWN_MUSHROOM, Rarity.RARE), + BURSTFIRE_DAGGER(Material.STONE_SWORD, Rarity.EPIC), BURSTMAW_DAGGER(Material.IRON_SWORD, Rarity.EPIC), @@ -618,6 +626,8 @@ public enum ItemType { CAN_OF_WORMS(Material.PLAYER_HEAD, Rarity.UNCOMMON), + CARNIVAL_TICKET(Material.NAME_TAG, Rarity.UNCOMMON), + CARROT(Material.CARROT, Rarity.COMMON), CARROT_BAIT(Material.PLAYER_HEAD, Rarity.COMMON), @@ -746,8 +756,12 @@ public enum ItemType { CLEAVER(Material.GOLDEN_SWORD, Rarity.UNCOMMON), + CLIPPED_WINGS(Material.FEATHER, Rarity.RARE), + COAL(Material.COAL, Rarity.COMMON), + COALROOT(Material.COAL, Rarity.UNCOMMON), + COAL_BLOCK(Material.COAL_BLOCK, Rarity.COMMON), COAL_MINION(Material.PLAYER_HEAD, Rarity.COMMON), @@ -800,6 +814,8 @@ public enum ItemType { CONTROL_SWITCH(Material.PLAYER_HEAD, Rarity.RARE), + COPPER_DYE(Material.ORANGE_DYE, Rarity.SPECIAL), + CORLEONITE(Material.PLAYER_HEAD, Rarity.EPIC), CORNFLOWER(Material.CORNFLOWER, Rarity.COMMON), @@ -854,8 +870,12 @@ public enum ItemType { CROCHET_TIGER_PLUSHIE(Material.PLAYER_HEAD, Rarity.EPIC), + CROPIE(Material.PLAYER_HEAD, Rarity.UNCOMMON), + CROPIE_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), + CROPSHOT_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + CROWN_OF_GREED(Material.GOLDEN_HELMET, Rarity.LEGENDARY), CRYPT_BOW(Material.BOW, Rarity.RARE), @@ -1006,6 +1026,8 @@ public enum ItemType { DECORATION_PINK_BERRY(Material.PLAYER_HEAD, Rarity.COMMON), + DEDICATION_I(Material.ENCHANTED_BOOK, Rarity.COMMON), + DEEP_ROOT(Material.PLAYER_HEAD, Rarity.UNCOMMON), DEFENSE_ENRICHMENT(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1120,6 +1142,8 @@ public enum ItemType { DUNGEON_SACK(Material.PLAYER_HEAD, Rarity.COMMON), + DUNG_DYE(Material.BROWN_DYE, Rarity.EPIC), + DWARF_TURTLE_SHELMET(Material.PLAYER_HEAD, Rarity.EPIC), DWARVEN_SACK(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1476,12 +1500,16 @@ public enum ItemType { ETERNAL_FLAME_RING(Material.PLAYER_HEAD, Rarity.UNCOMMON), + ETHEREAL_VINE(Material.VINE, Rarity.RARE), + ETHERWARP_CONDUIT(Material.PLAYER_HEAD, Rarity.EPIC), ETHERWARP_MERGER(Material.PLAYER_HEAD, Rarity.EPIC), EVERBURNING_FLAME(Material.PLAYER_HEAD, Rarity.EPIC), + EVERGREEN_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + EXCEEDINGLY_RARE_ENDER_ARTIFACT_UPGRADER(Material.PLAYER_HEAD, Rarity.LEGENDARY), EXPERIENCE_BOTTLE(Material.EXPERIENCE_BOTTLE, Rarity.COMMON), @@ -1506,6 +1534,8 @@ public enum ItemType { FARMING_EXP_BOOST_RARE(Material.IRON_HOE, Rarity.COMMON), + FARMING_EXP_BOOST_UNCOMMON(Material.IRON_HOE, Rarity.UNCOMMON), + FARMING_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), FARMING_WAND(Material.GOLDEN_HOE, Rarity.RARE), @@ -1530,6 +1560,8 @@ public enum ItemType { FERMENTED_SPIDER_EYE(Material.FERMENTED_SPIDER_EYE, Rarity.COMMON), + FERMENTO(Material.PLAYER_HEAD, Rarity.EPIC), + FERMENTO_ARTIFACT(Material.PLAYER_HEAD, Rarity.RARE), FERN(Material.FERN, Rarity.COMMON), @@ -1552,6 +1584,8 @@ public enum ItemType { FINE_CITRINE_GEM(Material.PLAYER_HEAD, Rarity.RARE), + FINE_FLOUR(Material.PLAYER_HEAD, Rarity.UNCOMMON), + FINE_JADE_GEM(Material.PLAYER_HEAD, Rarity.RARE), FINE_JASPER_GEM(Material.PLAYER_HEAD, Rarity.RARE), @@ -1576,6 +1610,8 @@ public enum ItemType { FIRE_FURY_STAFF(Material.BLAZE_ROD, Rarity.EPIC), + FIRE_IN_A_BOTTLE(Material.EXPERIENCE_BOTTLE, Rarity.RARE), + FIRE_RESISTANCE_POTION(Material.POTION, Rarity.COMMON), FIRE_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1656,6 +1692,8 @@ public enum ItemType { FLOWERING_AZALEA_LEAVES(Material.FLOWERING_AZALEA_LEAVES, Rarity.COMMON), + FLOWERING_BOUQUET(Material.PLAYER_HEAD, Rarity.RARE), + FLOWER_CRYSTAL(Material.PLAYER_HEAD, Rarity.COMMON), FLOWER_MINION(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1686,6 +1724,8 @@ public enum ItemType { FROZEN_SCYTHE(Material.IRON_HOE, Rarity.RARE), + FRUIT_BOWL(Material.BOWL, Rarity.RARE), + FTX_3070(Material.PLAYER_HEAD, Rarity.RARE), FUMING_POTATO_BOOK(Material.BOOK, Rarity.EPIC), @@ -1892,6 +1932,10 @@ public enum ItemType { GREAT_WHITE_SHARK_TOOTH(Material.GHAST_TEAR, Rarity.LEGENDARY), + GREENHOUSE_BLUEPRINT(Material.PAPER, Rarity.RARE), + + GREEN_BANDANA(Material.LIME_BANNER, Rarity.EPIC), + GREEN_BANNER(Material.GREEN_BANNER, Rarity.COMMON), GREEN_BED(Material.GREEN_BED, Rarity.COMMON), @@ -1918,6 +1962,8 @@ public enum ItemType { GREEN_TERRACOTTA(Material.GREEN_TERRACOTTA, Rarity.COMMON), + GREEN_THUMB_I(Material.ENCHANTED_BOOK, Rarity.COMMON), + GREEN_WOOL(Material.GREEN_WOOL, Rarity.COMMON), GRIFFIN_FEATHER(Material.FEATHER, Rarity.RARE), @@ -1940,6 +1986,8 @@ public enum ItemType { HARVESTING_SWORD(Material.DIAMOND_SWORD, Rarity.UNCOMMON), + HARVEST_HARBINGER_POTION(Material.POTION, Rarity.RARE), + HASTE_ARTIFACT(Material.PLAYER_HEAD, Rarity.EPIC), HASTE_POTION_1(Material.POTION, Rarity.COMMON), @@ -2028,6 +2076,8 @@ public enum ItemType { HURRICANE_BOW(Material.BOW, Rarity.EPIC), + HYPERCHARGE_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + HYPERION(Material.IRON_SWORD, Rarity.LEGENDARY), HYPER_CATALYST(Material.END_STONE, Rarity.EPIC), @@ -2074,6 +2124,8 @@ public enum ItemType { INVISIBILITY_POTION(Material.POTION, Rarity.COMMON), + IRIDIUM(Material.PRISMARINE_CRYSTALS, Rarity.EPIC), + IRON_AXE(Material.IRON_AXE, Rarity.COMMON), IRON_BARS(Material.IRON_BARS, Rarity.COMMON), @@ -2114,6 +2166,8 @@ public enum ItemType { JACK_O_LANTERN(Material.JACK_O_LANTERN, Rarity.COMMON), + JELLY(Material.SLIME_BALL, Rarity.COMMON), + JERRY_STAFF(Material.GOLDEN_HORSE_ARMOR, Rarity.EPIC), JERRY_TALISMAN_BLUE(Material.PLAYER_HEAD, Rarity.RARE), @@ -2436,6 +2490,8 @@ public enum ItemType { MANGROVE_WOOD(Material.MANGROVE_WOOD, Rarity.COMMON), + MANTID_CLAW(Material.RABBIT_FOOT, Rarity.RARE), + MASTER_SKULL_TIER_1(Material.PLAYER_HEAD, Rarity.COMMON), MASTER_SKULL_TIER_2(Material.PLAYER_HEAD, Rarity.COMMON), @@ -2454,6 +2510,8 @@ public enum ItemType { MAWDUST_DAGGER(Material.IRON_SWORD, Rarity.RARE), + MECHAMIND_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + MEDIUM_AGRONOMY_SACK(Material.PLAYER_HEAD, Rarity.RARE), MEDIUM_BACKPACK(Material.PLAYER_HEAD, Rarity.RARE), @@ -2578,6 +2636,8 @@ public enum ItemType { MOVE_JERRY(Material.VILLAGER_SPAWN_EGG, Rarity.COMMON), + MOVE_SAM(Material.VILLAGER_SPAWN_EGG, Rarity.COMMON), + MUSHROOM(Material.BROWN_MUSHROOM, Rarity.COMMON), MUSHROOM_BOOTS(Material.LEATHER_BOOTS, Rarity.COMMON), @@ -2598,6 +2658,8 @@ public enum ItemType { MYCELIUM(Material.MYCELIUM, Rarity.COMMON), + MYSTERIOUS_CROP(Material.DEAD_BUSH, Rarity.COMMON), + NECROMANCER_SWORD(Material.IRON_SWORD, Rarity.LEGENDARY), NECRONS_HANDLE(Material.STICK, Rarity.COMMON), @@ -2728,8 +2790,12 @@ public enum ItemType { ORNATE_ZOMBIE_SWORD(Material.GOLDEN_SWORD, Rarity.EPIC), + OVERDRIVE_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + OVERFLUX_CAPACITOR(Material.QUARTZ, Rarity.EPIC), + OVERGROWN_GRASS(Material.GRASS_BLOCK, Rarity.RARE), + OXEYE_DAISY(Material.OXEYE_DAISY, Rarity.COMMON), PACKED_ICE(Material.PACKED_ICE, Rarity.COMMON), @@ -2820,6 +2886,8 @@ public enum ItemType { PESTHUNTER_RING(Material.PLAYER_HEAD, Rarity.RARE), + PET_CAKE(Material.CAKE, Rarity.COMMON), + PET_LUCK_POTION_1(Material.POTION, Rarity.COMMON), PHANTOM_HOOK(Material.PLAYER_HEAD, Rarity.RARE), @@ -3034,6 +3102,8 @@ public enum ItemType { QUARTZ_STAIRS(Material.QUARTZ_STAIRS, Rarity.COMMON), + QUICKDRAW_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + RABBIT(Material.RABBIT, Rarity.COMMON), RABBIT_AXE(Material.STONE_AXE, Rarity.UNCOMMON), @@ -3052,6 +3122,8 @@ public enum ItemType { RAIL(Material.RAIL, Rarity.COMMON), + RAREFINDER_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + RAW_BEEF(Material.BEEF, Rarity.COMMON), RAW_CHICKEN(Material.CHICKEN, Rarity.COMMON), @@ -3914,6 +3986,12 @@ public enum ItemType { SOUL_WHIP(Material.FISHING_ROD, Rarity.EPIC), + SOWDUST(Material.GREEN_DYE, Rarity.COMMON), + + SOWLEDGE_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + + SPACE_HELMET(Material.PLAYER_HEAD, Rarity.SPECIAL), + SPEEDSTER_ROD(Material.FISHING_ROD, Rarity.UNCOMMON), SPEEDY_LINE(Material.PLAYER_HEAD, Rarity.RARE), @@ -4000,8 +4078,14 @@ public enum ItemType { SPRUCE_WOOD(Material.SPRUCE_WOOD, Rarity.COMMON), + SQUASH(Material.PLAYER_HEAD, Rarity.RARE), + SQUASH_RING(Material.PLAYER_HEAD, Rarity.UNCOMMON), + SQUEAKY_MOUSEMAT(Material.GRAY_CARPET, Rarity.EPIC), + + SQUEAKY_TOY(Material.RABBIT_FOOT, Rarity.RARE), + SQUIRE_BOOTS(Material.LEATHER_BOOTS, Rarity.UNCOMMON), SQUIRE_CHESTPLATE(Material.IRON_CHESTPLATE, Rarity.UNCOMMON), @@ -4182,8 +4266,14 @@ public enum ItemType { SUMSUNG_GG_ABICASE(Material.PLAYER_HEAD, Rarity.COMMON), + SUNDER_I(Material.ENCHANTED_BOOK, Rarity.COMMON), + + SUNDER_VI_BOOK(Material.ENCHANTED_BOOK, Rarity.RARE), + SUNFLOWER(Material.SUNFLOWER, Rarity.COMMON), + SUNFLOWER_OIL(Material.HONEY_BOTTLE, Rarity.UNCOMMON), + SUPERBOOM_TNT(Material.TNT, Rarity.RARE), SUPERLITE_MOTOR(Material.PLAYER_HEAD, Rarity.RARE), @@ -4204,6 +4294,8 @@ public enum ItemType { SWORD_OF_REVELATIONS(Material.WOODEN_SWORD, Rarity.EPIC), + SYNTHESIS_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + SYNTHETIC_HEART(Material.PLAYER_HEAD, Rarity.RARE), TACTICIAN_MURDER_WEAPON(Material.STONE_SWORD, Rarity.RARE), @@ -4316,6 +4408,8 @@ public enum ItemType { VENOMS_TOUCH(Material.BOW, Rarity.EPIC), + VERMIN_VAPORIZER_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + VILLAGE_AFFINITY_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), VINES(Material.VINE, Rarity.COMMON), @@ -4430,6 +4524,8 @@ public enum ItemType { WILD_ROSE(Material.ROSE_BUSH, Rarity.COMMON), + WILD_STRAWBERRY_DYE(Material.PLAYER_HEAD, Rarity.LEGENDARY), + WILSON_ENGINEERING_PLANS(Material.PAPER, Rarity.LEGENDARY), WINTER_ROD(Material.FISHING_ROD, Rarity.RARE), @@ -4474,6 +4570,8 @@ public enum ItemType { WORM_MEMBRANE(Material.ROTTEN_FLESH, Rarity.UNCOMMON), + WRIGGLING_LARVA(Material.STRING, Rarity.RARE), + WYLD_SWORD(Material.WOODEN_SWORD, Rarity.UNCOMMON), YELLOW_BANNER(Material.YELLOW_BANNER, Rarity.COMMON), diff --git a/commons/src/main/java/net/swofty/commons/ServerType.java b/commons/src/main/java/net/swofty/commons/ServerType.java index 98f83b61b9..9ff2e9b13f 100644 --- a/commons/src/main/java/net/swofty/commons/ServerType.java +++ b/commons/src/main/java/net/swofty/commons/ServerType.java @@ -18,6 +18,7 @@ public enum ServerType { SKYBLOCK_GALATEA(true), SKYBLOCK_BACKWATER_BAYOU(true), SKYBLOCK_JERRYS_WORKSHOP(true), + SKYBLOCK_GARDEN(true), PROTOTYPE_LOBBY(false), BEDWARS_LOBBY(false), BEDWARS_GAME(false), diff --git a/commons/src/main/java/net/swofty/commons/ServiceType.java b/commons/src/main/java/net/swofty/commons/ServiceType.java index 9bc9e89208..1179d05ce5 100644 --- a/commons/src/main/java/net/swofty/commons/ServiceType.java +++ b/commons/src/main/java/net/swofty/commons/ServiceType.java @@ -8,6 +8,7 @@ public enum ServiceType { DATA_MUTEX, PARTY, DARK_AUCTION, + JACOBS_CONTEST, ORCHESTRATOR, FRIEND, REPLAY, diff --git a/commons/src/main/java/net/swofty/commons/protocol/objects/jacobscontest/GetJacobContestScheduleProtocol.java b/commons/src/main/java/net/swofty/commons/protocol/objects/jacobscontest/GetJacobContestScheduleProtocol.java new file mode 100644 index 0000000000..e499ade8e0 --- /dev/null +++ b/commons/src/main/java/net/swofty/commons/protocol/objects/jacobscontest/GetJacobContestScheduleProtocol.java @@ -0,0 +1,111 @@ +package net.swofty.commons.protocol.objects.jacobscontest; + +import net.swofty.commons.protocol.RedisProtocol; +import net.swofty.commons.protocol.Serializer; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class GetJacobContestScheduleProtocol extends RedisProtocol< + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage, + GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse> { + + @Override + public Serializer getSerializer() { + return new Serializer<>() { + @Override + public String serialize(GetJacobContestScheduleMessage value) { + JSONObject json = new JSONObject(); + json.put("calendarElapsed", value.calendarElapsed); + json.put("upcomingCount", value.upcomingCount); + return json.toString(); + } + + @Override + public GetJacobContestScheduleMessage deserialize(String json) { + JSONObject jsonObject = new JSONObject(json); + return new GetJacobContestScheduleMessage( + jsonObject.optLong("calendarElapsed", 0L), + jsonObject.optInt("upcomingCount", 3) + ); + } + + @Override + public GetJacobContestScheduleMessage clone(GetJacobContestScheduleMessage value) { + return new GetJacobContestScheduleMessage(value.calendarElapsed, value.upcomingCount); + } + }; + } + + @Override + public Serializer getReturnSerializer() { + return new Serializer<>() { + @Override + public String serialize(GetJacobContestScheduleResponse value) { + JSONObject json = new JSONObject(); + json.put("year", value.year); + json.put("activeIndex", value.activeIndex); + + JSONArray contests = new JSONArray(); + for (ContestScheduleEntry entry : value.entries) { + JSONObject item = new JSONObject(); + item.put("index", entry.index); + item.put("startTime", entry.startTime); + item.put("endTime", entry.endTime); + item.put("day", entry.day); + item.put("crops", entry.crops); + contests.put(item); + } + json.put("entries", contests); + return json.toString(); + } + + @Override + public GetJacobContestScheduleResponse deserialize(String json) { + JSONObject jsonObject = new JSONObject(json); + List entries = new ArrayList<>(); + JSONArray contests = jsonObject.optJSONArray("entries"); + if (contests != null) { + for (int i = 0; i < contests.length(); i++) { + JSONObject item = contests.getJSONObject(i); + JSONArray crops = item.optJSONArray("crops"); + List cropList = new ArrayList<>(); + if (crops != null) { + for (int cropIndex = 0; cropIndex < crops.length(); cropIndex++) { + cropList.add(crops.getString(cropIndex)); + } + } + entries.add(new ContestScheduleEntry( + item.optInt("index", 0), + item.optLong("startTime", 0L), + item.optLong("endTime", 0L), + item.optInt("day", 1), + cropList + )); + } + } + return new GetJacobContestScheduleResponse( + jsonObject.optInt("year", 1), + jsonObject.optInt("activeIndex", -1), + entries + ); + } + + @Override + public GetJacobContestScheduleResponse clone(GetJacobContestScheduleResponse value) { + return new GetJacobContestScheduleResponse(value.year, value.activeIndex, List.copyOf(value.entries)); + } + }; + } + + public record GetJacobContestScheduleMessage(long calendarElapsed, int upcomingCount) { + } + + public record GetJacobContestScheduleResponse(int year, int activeIndex, List entries) { + } + + public record ContestScheduleEntry(int index, long startTime, long endTime, int day, List crops) { + } +} diff --git a/configuration/skyblock/furniture/composter.json b/configuration/skyblock/furniture/composter.json new file mode 100644 index 0000000000..07ac7fb7ec --- /dev/null +++ b/configuration/skyblock/furniture/composter.json @@ -0,0 +1,2298 @@ +[ + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.28325535731151597, + "y": -0.14751567249814457, + "z": 0.05476482307119923 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 30.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.2479427220786352, + "y": -0.26620865616828837, + "z": -0.14151185870298022 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.2850110769857519, + "y": -0.26490130645890986, + "z": -0.12384993274281086 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.2525528610050696, + "y": -0.2695722952391151, + "z": 0.18054223363595057 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:coarse_dirt", + "count": 1, + "snbt": "{count:1,id:\"minecraft:coarse_dirt\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.27854664113262295, + "y": -0.10359029204920489, + "z": 0.29165423982437133 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": -50.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.2582553672201744, + "y": -0.3092635500163823, + "z": 0.17800267366044764 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:coarse_dirt", + "count": 1, + "snbt": "{count:1,id:\"minecraft:coarse_dirt\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.28355292846218383, + "y": -0.17811723640500077, + "z": -0.30127602775723616 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:coarse_dirt", + "count": 1, + "snbt": "{count:1,id:\"minecraft:coarse_dirt\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.2895777759456042, + "y": -0.2456934409338487, + "z": -0.25392506661341585 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:coarse_dirt", + "count": 1, + "snbt": "{count:1,id:\"minecraft:coarse_dirt\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.011908153417509837, + "y": -0.467636689115551, + "z": 0.020142358638285174 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.0021778731874793067, + "y": -0.4463782718631961, + "z": -0.15423002397761465 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.022245799849754277, + "y": 0.2367874611100831, + "z": -0.504194098771503 + }, + "rotation": { + "yaw": 45.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"\"},{color:\"blue\",text:\"Chimney (stone bricks)\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,-1432272096],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjNhMjYwOTUxYmMyZjU4YjRiNTFjZGFhZDlkMTM1ODUxNTlmNDc0NjNhODUxMWVmY2I3MmM1NjI4MjNmNzdkIn19fQ==\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -19.0, + "y": 0.0, + "z": -6.0 + }, + "rightArm": { + "x": -45.0, + "y": 0.0, + "z": 0.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.0028778484866780474, + "y": -0.5319378161581625, + "z": 0.18792872706569952 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.008164197539469953, + "y": -0.5510854459261765, + "z": -0.3120313388397413 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.327316724155553, + "y": -0.47445769727131903, + "z": 0.36680293104064177 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"Computer Tower\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,178939960],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2I5MjVkM2E1Mjc1OWZkZThjMDI1ODg3MWZlZmQ5MTQxZTVjOTdmZGY0NTNhZjNkZjIxMTA0Y2M4YzQ4OCJ9fX0=\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.2998347258386911, + "y": -0.49757206412728294, + "z": 0.36351526011259594 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"Computer Tower\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,178939960],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2I5MjVkM2E1Mjc1OWZkZThjMDI1ODg3MWZlZmQ5MTQxZTVjOTdmZGY0NTNhZjNkZjIxMTA0Y2M4YzQ4OCJ9fX0=\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.5372706556904472, + "y": -0.4380373331975278, + "z": 0.020699753706598045 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.5383862976464844, + "y": -0.45842752511121887, + "z": 0.024311192107862922 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.5301115276066479, + "y": -0.4465870808454042, + "z": -0.15450066952939423 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.2765570371441708, + "y": -0.4544659942479967, + "z": -0.4736787866548262 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 60.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.5300266508485425, + "y": -0.4592848713223958, + "z": -0.14999895624134396 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.2889160259418464, + "y": -0.46018385463017353, + "z": -0.46853282844824307 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 60.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.530533512766981, + "y": -0.5354751690027797, + "z": 0.17631481593241816 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.5254656240836493, + "y": -0.5490085090034853, + "z": 0.18720223853728513 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.5228497143977666, + "y": -0.5346238883667809, + "z": -0.32309841219960944 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.5236897616055511, + "y": -0.5395366206154506, + "z": -0.31375398299445223 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 60.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.0210946843636588, + "y": 0.3668285568585361, + "z": -0.8143954929311654 + }, + "rotation": { + "yaw": 45.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"\"},{color:\"blue\",text:\"Chimney (stone bricks)\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,-1432272096],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjNhMjYwOTUxYmMyZjU4YjRiNTFjZGFhZDlkMTM1ODUxNTlmNDc0NjNhODUxMWVmY2I3MmM1NjI4MjNmNzdkIn19fQ==\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -19.0, + "y": 0.0, + "z": -6.0 + }, + "rightArm": { + "x": -45.0, + "y": 0.0, + "z": 0.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.8016366320478436, + "y": -0.36786082051364133, + "z": 0.15423046198072043 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"Recycling Bin (gray, empty)\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,-1432203000],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGVkNjc5OTE0OTc4OGI5ZTkwMTY4MTFkM2EzZDBlZDFmNTUyNTMwZDY3Zjk4Njk0NTAzMmQ2ZTQzOWZhODk5ZCJ9fX0=\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.6921720691008364, + "y": -0.47292603423781543, + "z": -0.3693551102590469 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_brick_slab", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_brick_slab\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.8234143023751539, + "y": -0.3723799950181217, + "z": 0.159305008894993 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:player_head", + "count": 1, + "snbt": "{components:{\"minecraft:custom_name\":{extra:[{color:\"blue\",text:\"Recycling Bin (gray, empty)\"}],italic:0b,text:\"\"},\"minecraft:profile\":{id:[I;67411088,-739686879,-1666252800,-1432203000],properties:[{name:\"textures\",value:\"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGVkNjc5OTE0OTc4OGI5ZTkwMTY4MTFkM2EzZDBlZDFmNTUyNTMwZDY3Zjk4Njk0NTAzMmQ2ZTQzOWZhODk5ZCJ9fX0=\"}]}},count:1,id:\"minecraft:player_head\"}" + } + }, + "pose": { + "head": { + "x": 180.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.6817423060788155, + "y": -0.48585997912037726, + "z": -0.39205529338618916 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_brick_slab", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_brick_slab\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.3224132804458417, + "y": -1.2028034963656182, + "z": 0.24491932248566073 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:furnace", + "count": 1, + "snbt": "{count:1,id:\"minecraft:furnace\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.31750074624672386, + "y": -1.2063025610847546, + "z": 0.2550061778611088 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:furnace", + "count": 1, + "snbt": "{count:1,id:\"minecraft:furnace\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.30292697714223316, + "y": -1.208616633644695, + "z": -0.3756276166372423 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:furnace", + "count": 1, + "snbt": "{count:1,id:\"minecraft:furnace\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.32772923078398897, + "y": -1.2057550263546375, + "z": -0.3827406227824177 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:furnace", + "count": 1, + "snbt": "{count:1,id:\"minecraft:furnace\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.6843788705789802, + "y": -1.1034560420990545, + "z": -0.3844094282828756 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:cobblestone_slab", + "count": 1, + "snbt": "{count:1,id:\"minecraft:cobblestone_slab\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.6776125285526451, + "y": -1.1132953117726032, + "z": -0.36897124566513284 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:cobblestone_slab", + "count": 1, + "snbt": "{count:1,id:\"minecraft:cobblestone_slab\"}" + } + }, + "pose": { + "head": { + "x": 90.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.2229023699042454, + "y": -1.2981002578128624, + "z": 0.4331579057363264 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_button", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_button\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 90.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.5910859575154905, + "y": 0.6086526733260484, + "z": -1.1100769270250872 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:charcoal", + "count": 1, + "snbt": "{count:1,id:\"minecraft:charcoal\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -91.0, + "y": 0.0, + "z": 166.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.22773800546546674, + "y": -1.30341417805856, + "z": -0.5529549157569669 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_button", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_button\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 90.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.7286512251987052, + "y": -1.2918521690364457, + "z": 0.427042953012009 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_button", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_button\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 90.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.6993303672992557, + "y": -1.305855577605442, + "z": -0.5604504881120498 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:stone_button", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stone_button\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 90.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + } +] \ No newline at end of file diff --git a/configuration/skyblock/furniture/desk.json b/configuration/skyblock/furniture/desk.json new file mode 100644 index 0000000000..6f9458fa58 --- /dev/null +++ b/configuration/skyblock/furniture/desk.json @@ -0,0 +1,282 @@ +[ + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.16251223534345627, + "y": 0.3998425155878067, + "z": -0.125 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:oak_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:oak_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": -13.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.22501223534345627, + "y": 0.3998425155878067, + "z": -0.09375 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:oak_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:oak_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": -13.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.1875, + "y": 0.4375, + "z": -0.09375 + }, + "rotation": { + "yaw": -90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:white_carpet", + "count": 1, + "snbt": "{count:1,id:\"minecraft:white_carpet\"}" + } + }, + "pose": { + "head": { + "x": -13.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.21875, + "y": 0.4375, + "z": -0.09375 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:white_carpet", + "count": 1, + "snbt": "{count:1,id:\"minecraft:white_carpet\"}" + } + }, + "pose": { + "head": { + "x": -13.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.15625, + "y": 0.5625, + "z": -0.3125 + }, + "rotation": { + "yaw": 0.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:stick", + "count": 1, + "snbt": "{count:1,id:\"minecraft:stick\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -10.0, + "y": 0.0, + "z": 2.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + } +] \ No newline at end of file diff --git a/configuration/skyblock/furniture/swords_hub.json b/configuration/skyblock/furniture/swords_hub.json new file mode 100644 index 0000000000..99a0f08487 --- /dev/null +++ b/configuration/skyblock/furniture/swords_hub.json @@ -0,0 +1,114 @@ +[ + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.021884863069715266, + "y": -0.16696476165546414, + "z": -0.2075808103294321 + }, + "rotation": { + "yaw": 90.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:wooden_sword", + "count": 1, + "snbt": "{count:1,id:\"minecraft:wooden_sword\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": 71.0, + "y": 50.0, + "z": -6.0 + }, + "leftLeg": { + "x": -19.0, + "y": 0.0, + "z": 15.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.5064550143374333, + "y": -0.25, + "z": 0.07497938488566547 + }, + "rotation": { + "yaw": -135.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:iron_sword", + "count": 1, + "snbt": "{count:1,id:\"minecraft:iron_sword\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": 71.0, + "y": 25.0, + "z": -31.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -4.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + } +] \ No newline at end of file diff --git a/configuration/skyblock/furniture/visitor_logbook.json b/configuration/skyblock/furniture/visitor_logbook.json new file mode 100644 index 0000000000..bf4263d467 --- /dev/null +++ b/configuration/skyblock/furniture/visitor_logbook.json @@ -0,0 +1,450 @@ +[ + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.03789028525352478, + "y": -0.34589673293088197, + "z": 0.18446465581655502 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:snow_block", + "count": 1, + "snbt": "{count:1,id:\"minecraft:snow_block\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -14.0, + "y": -42.0, + "z": 0.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.14960971474647522, + "y": -0.35754041117849056, + "z": 0.18446465581655502 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:snow_block", + "count": 1, + "snbt": "{count:1,id:\"minecraft:snow_block\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -14.0, + "y": -42.0, + "z": 0.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.25, + "y": -0.7188258401906467, + "z": 0.0 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:music_disc_stal", + "count": 1, + "snbt": "{components:{\"minecraft:jukebox_playable\":\"minecraft:stal\"},count:1,id:\"minecraft:music_disc_stal\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -87.0, + "y": 20.0, + "z": 91.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.25, + "y": -0.7482019052020519, + "z": 0.25 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:music_disc_stal", + "count": 1, + "snbt": "{components:{\"minecraft:jukebox_playable\":\"minecraft:stal\"},count:1,id:\"minecraft:music_disc_stal\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -87.0, + "y": 20.0, + "z": 91.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.25, + "y": -0.7419362751703602, + "z": 0.46875 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": true, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "mainhand": { + "id": "minecraft:music_disc_stal", + "count": 1, + "snbt": "{components:{\"minecraft:jukebox_playable\":\"minecraft:stal\"},count:1,id:\"minecraft:music_disc_stal\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -87.0, + "y": 20.0, + "z": 91.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.03125, + "y": -1.1880634582254146, + "z": -0.03125 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:red_carpet", + "count": 1, + "snbt": "{count:1,id:\"minecraft:red_carpet\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": 0.0, + "y": -1.3420355261851853, + "z": 0.0 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:heavy_weighted_pressure_plate", + "count": 1, + "snbt": "{count:1,id:\"minecraft:heavy_weighted_pressure_plate\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + }, + { + "type": "minecraft:armor_stand", + "position": { + "x": -0.03125, + "y": -1.353694446457041, + "z": 0.0 + }, + "rotation": { + "yaw": -180.0, + "pitch": 0.0 + }, + "invisible": true, + "small": false, + "marker": false, + "showArms": true, + "showBasePlate": true, + "equipment": { + "head": { + "id": "minecraft:red_carpet", + "count": 1, + "snbt": "{count:1,id:\"minecraft:red_carpet\"}" + } + }, + "pose": { + "head": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "body": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "leftArm": { + "x": -10.0, + "y": 0.0, + "z": -10.0 + }, + "rightArm": { + "x": -15.0, + "y": 0.0, + "z": 10.0 + }, + "leftLeg": { + "x": -1.0, + "y": 0.0, + "z": -1.0 + }, + "rightLeg": { + "x": 1.0, + "y": 0.0, + "z": 1.0 + } + } + } +] \ No newline at end of file diff --git a/configuration/skyblock/garden/barn_skins.yml b/configuration/skyblock/garden/barn_skins.yml new file mode 100644 index 0000000000..de5a4f6b58 --- /dev/null +++ b/configuration/skyblock/garden/barn_skins.yml @@ -0,0 +1,38 @@ +locked_plot_schematic: locked.litematic +skins: + - id: default + display_name: Default Barn + rarity: COMMON + schematic: default.litematic + unlock_source: Default + offsets: { x: 0, y: 0, z: 0 } + - id: spruce + display_name: Medieval Barn Skin + rarity: UNCOMMON + schematic: spruce.litematic + unlock_source: Garden Level III + offsets: { x: 0, y: 0, z: 0 } + - id: red + display_name: Red Barn Skin + rarity: UNCOMMON + schematic: red.litematic + unlock_source: Garden Level IX + offsets: { x: 0, y: 0, z: 0 } + - id: sunny + display_name: Sunny Barn Skin + rarity: UNCOMMON + schematic: sunny.litematic + unlock_source: Garden Level VI + offsets: { x: 0, y: 0, z: 0 } + - id: cabin + display_name: Cabin Barn Skin + rarity: RARE + schematic: cabin.litematic + unlock_source: Garden Level XI + offsets: { x: 0, y: 0, z: 0 } + - id: chocolate + display_name: Chocolate Barn Skin + rarity: RARE + schematic: chocolate.litematic + unlock_source: Barn Skin Cosmetic + offsets: { x: 0, y: 0, z: 0 } diff --git a/configuration/skyblock/garden/chips.yml b/configuration/skyblock/garden/chips.yml new file mode 100644 index 0000000000..26fddf8c23 --- /dev/null +++ b/configuration/skyblock/garden/chips.yml @@ -0,0 +1,94 @@ +rarity_thresholds: + RARE: 1 + EPIC: 4 + LEGENDARY: 16 +garden_sowdust: + formula: ceil(total_farming_fortune / 100) / block_break_multiplier + double_break_crops: + - SUGAR_CANE + - CACTUS +greenhouse_crop_multipliers: + WHEAT: 1 + CARROT: 3.5 + POTATO: 3 + PUMPKIN: 0.85 + SUGAR_CANE: 2 + MELON_SLICE: 4 + CACTUS: 1.5 + COCOA_BEANS: 2 + MUSHROOM: 0.95 + NETHER_WART: 3 + MOONFLOWER: 2 + SUNFLOWER: 2 + WILD_ROSE: 2 +level_costs: + - { level: 2, cost: 100000 } + - { level: 3, cost: 200000 } + - { level: 4, cost: 300000 } + - { level: 5, cost: 400000 } + - { level: 6, cost: 550000 } + - { level: 7, cost: 700000 } + - { level: 8, cost: 850000 } + - { level: 9, cost: 1000000 } + - { level: 10, cost: 1150000 } + - { level: 11, cost: 1300000 } + - { level: 12, cost: 1450000 } + - { level: 13, cost: 1600000 } + - { level: 14, cost: 1750000 } + - { level: 15, cost: 1900000 } + - { level: 16, cost: 2050000 } + - { level: 17, cost: 2200000 } + - { level: 18, cost: 2350000 } + - { level: 19, cost: 2500000 } + - { level: 20, cost: 2650000 } +chips: + - id: VERMIN_VAPORIZER_GARDEN_CHIP + display_name: Vermin Vaporizer Chip + acquisition: Dragonfly Pest 1% + effects: { rare_per_level: 3, epic_per_level: 4, legendary_per_level: 5 } + cap: { rare: 30, epic: 60, legendary: 100 } + - id: SYNTHESIS_GARDEN_CHIP + display_name: Synthesis Chip + acquisition: Greenhouse Harvest Bounty 3% + effects: { rare_per_level: 1, epic_per_level: 1.5, legendary_per_level: 2 } + cap: { rare: 10, epic: 22.5, legendary: 40 } + - id: SOWLEDGE_GARDEN_CHIP + display_name: Sowledge Chip + acquisition: SkyMart 200 Copper + effects: { rare_per_level: 1, epic_per_level: 1.25, legendary_per_level: 1.5 } + cap: { rare: 10, epic: 18.75, legendary: 30 } + - id: MECHAMIND_GARDEN_CHIP + display_name: Mechamind Chip + acquisition: Anita 1 Gold Medal + effects: { rare_per_level: 1.5, epic_per_level: 2, legendary_per_level: 2.5 } + cap: { rare: 15, epic: 30, legendary: 50 } + - id: HYPERCHARGE_GARDEN_CHIP + display_name: Hypercharge Chip + acquisition: Visitor Offers 2% + effects: { rare_per_level: 3, epic_per_level: 4, legendary_per_level: 5 } + cap: { rare: 30, epic: 60, legendary: 100 } + - id: EVERGREEN_GARDEN_CHIP + display_name: Evergreen Chip + acquisition: Greenhouse Harvest Bounty 3% + effects: { rare_per_level: 2, epic_per_level: 2.5, legendary_per_level: 3 } + cap: { rare: 20, epic: 37.5, legendary: 60 } + - id: OVERDRIVE_GARDEN_CHIP + display_name: Overdrive Chip + acquisition: Anita 2 Gold Medals + effects: { rare_per_level: 5, epic_per_level: 6, legendary_per_level: 7 } + cap: { rare: 50, epic: 90, legendary: 140 } + - id: CROPSHOT_GARDEN_CHIP + display_name: Cropshot Chip + acquisition: SkyMart 500 Copper / Jeff First Chip + effects: { rare_per_level: 3, epic_per_level: 4, legendary_per_level: 5 } + cap: { rare: 30, epic: 60, legendary: 100 } + - id: QUICKDRAW_GARDEN_CHIP + display_name: Quickdraw Chip + acquisition: Visitor Offers 2% + effects: { rare_per_level: 1.5, epic_per_level: 2, legendary_per_level: 2.5 } + cap: { rare: 15, epic: 30, legendary: 50 } + - id: RAREFINDER_GARDEN_CHIP + display_name: Rarefinder Chip + acquisition: Crop Farming 0.00015% + effects: { rare_per_level: 2, epic_per_level: 2.5, legendary_per_level: 3 } + cap: { rare: 20, epic: 37.5, legendary: 60 } diff --git a/configuration/skyblock/garden/composter.yml b/configuration/skyblock/garden/composter.yml new file mode 100644 index 0000000000..9a1498fcfe --- /dev/null +++ b/configuration/skyblock/garden/composter.yml @@ -0,0 +1,117 @@ +base_production_seconds: 600 +base_organic_matter_cost: 4000 +base_fuel_cost: 2000 +branches: + speed: + unlock_garden_level: 2 + per_tier_percent: 20 + max_tier: 25 + multi_drop: + unlock_garden_level: 4 + per_tier_percent: 3 + max_tier: 25 + fuel_cap: + unlock_garden_level: 6 + per_tier_capacity: 30000 + base_capacity: 100000 + max_capacity: 850000 + organic_matter_cap: + unlock_garden_level: 8 + per_tier_capacity: 30000 + base_capacity: 40000 + max_capacity: 790000 + cost_reduction: + unlock_garden_level: 10 + per_tier_percent: 1 + max_tier: 25 +upgrade_tiers: + - { tier: 1, copper: 100, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 128, multi_drop_units: 0, fuel_cap_units: 0, organic_matter_cap_units: 0, cost_reduction_units: 32 } + - { tier: 2, copper: 150, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 2, multi_drop_units: 64, fuel_cap_units: 4, organic_matter_cap_units: 3, cost_reduction_units: 0 } + - { tier: 3, copper: 200, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 256, multi_drop_units: 2, fuel_cap_units: 2, organic_matter_cap_units: 2, cost_reduction_units: 2 } + - { tier: 4, copper: 250, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 4, multi_drop_units: 0, fuel_cap_units: 8, organic_matter_cap_units: 6, cost_reduction_units: 2 } + - { tier: 5, copper: 300, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 512, multi_drop_units: 4, fuel_cap_units: 4, organic_matter_cap_units: 4, cost_reduction_units: 4 } + - { tier: 6, copper: 350, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 8, multi_drop_units: 2, fuel_cap_units: 16, organic_matter_cap_units: 12, cost_reduction_units: 4 } + - { tier: 7, copper: 400, rare_crop_item: NONE, rare_crop_amount: 0, speed_units: 8, multi_drop_units: 8, fuel_cap_units: 8, organic_matter_cap_units: 7, cost_reduction_units: 8 } + - { tier: 8, copper: 500, rare_crop_item: CROPIE, rare_crop_amount: 3, speed_units: 16, multi_drop_units: 4, fuel_cap_units: 32, organic_matter_cap_units: 24, cost_reduction_units: 8 } + - { tier: 9, copper: 600, rare_crop_item: CROPIE, rare_crop_amount: 6, speed_units: 12, multi_drop_units: 16, fuel_cap_units: 16, organic_matter_cap_units: 10, cost_reduction_units: 16 } + - { tier: 10, copper: 700, rare_crop_item: CROPIE, rare_crop_amount: 12, speed_units: 32, multi_drop_units: 8, fuel_cap_units: 40, organic_matter_cap_units: 48, cost_reduction_units: 16 } + - { tier: 11, copper: 800, rare_crop_item: CROPIE, rare_crop_amount: 24, speed_units: 16, multi_drop_units: 32, fuel_cap_units: 32, organic_matter_cap_units: 16, cost_reduction_units: 24 } + - { tier: 12, copper: 900, rare_crop_item: CROPIE, rare_crop_amount: 48, speed_units: 40, multi_drop_units: 16, fuel_cap_units: 48, organic_matter_cap_units: 64, cost_reduction_units: 32 } + - { tier: 13, copper: 1000, rare_crop_item: CROPIE, rare_crop_amount: 96, speed_units: 24, multi_drop_units: 48, fuel_cap_units: 40, organic_matter_cap_units: 24, cost_reduction_units: 32 } + - { tier: 14, copper: 1200, rare_crop_item: SQUASH, rare_crop_amount: 3, speed_units: 48, multi_drop_units: 24, fuel_cap_units: 64, organic_matter_cap_units: 96, cost_reduction_units: 48 } + - { tier: 15, copper: 1400, rare_crop_item: SQUASH, rare_crop_amount: 6, speed_units: 32, multi_drop_units: 64, fuel_cap_units: 48, organic_matter_cap_units: 32, cost_reduction_units: 48 } + - { tier: 16, copper: 1600, rare_crop_item: SQUASH, rare_crop_amount: 12, speed_units: 64, multi_drop_units: 32, fuel_cap_units: 96, organic_matter_cap_units: 128, cost_reduction_units: 64 } + - { tier: 17, copper: 1800, rare_crop_item: SQUASH, rare_crop_amount: 24, speed_units: 40, multi_drop_units: 80, fuel_cap_units: 64, organic_matter_cap_units: 40, cost_reduction_units: 64 } + - { tier: 18, copper: 2000, rare_crop_item: SQUASH, rare_crop_amount: 48, speed_units: 96, multi_drop_units: 48, fuel_cap_units: 128, organic_matter_cap_units: 160, cost_reduction_units: 80 } + - { tier: 19, copper: 2250, rare_crop_item: SQUASH, rare_crop_amount: 96, speed_units: 48, multi_drop_units: 112, fuel_cap_units: 96, organic_matter_cap_units: 48, cost_reduction_units: 96 } + - { tier: 20, copper: 2500, rare_crop_item: FERMENTO, rare_crop_amount: 3, speed_units: 128, multi_drop_units: 64, fuel_cap_units: 192, organic_matter_cap_units: 208, cost_reduction_units: 112 } + - { tier: 21, copper: 2750, rare_crop_item: FERMENTO, rare_crop_amount: 6, speed_units: 64, multi_drop_units: 144, fuel_cap_units: 144, organic_matter_cap_units: 64, cost_reduction_units: 128 } + - { tier: 22, copper: 3000, rare_crop_item: FERMENTO, rare_crop_amount: 12, speed_units: 192, multi_drop_units: 96, fuel_cap_units: 288, organic_matter_cap_units: 256, cost_reduction_units: 160 } + - { tier: 23, copper: 3300, rare_crop_item: CONDENSED_FERMENTO, rare_crop_amount: 2, speed_units: 96, multi_drop_units: 176, fuel_cap_units: 192, organic_matter_cap_units: 96, cost_reduction_units: 160 } + - { tier: 24, copper: 3600, rare_crop_item: CONDENSED_FERMENTO, rare_crop_amount: 4, speed_units: 256, multi_drop_units: 128, fuel_cap_units: 384, organic_matter_cap_units: 320, cost_reduction_units: 224 } + - { tier: 25, copper: 4000, rare_crop_item: CONDENSED_FERMENTO, rare_crop_amount: 8, speed_units: 128, multi_drop_units: 224, fuel_cap_units: 256, organic_matter_cap_units: 128, cost_reduction_units: 192 } +organic_matter_values: + CROPIE: 2500 + SQUASH: 10000 + FERMENTO: 20000 + CONDENSED_FERMENTO: 180000 + FLOWERING_BOUQUET: 6000 + FINE_FLOUR: 150 + WHEAT: 1 + ENCHANTED_BREAD: 60 + ENCHANTED_WHEAT: 160 + ENCHANTED_HAY_BALE: 25600 + SEEDS: 1 + ENCHANTED_SEEDS: 160 + BOX_OF_SEEDS: 25600 + CARROT: 0.29 + ENCHANTED_CARROT: 46.4 + ENCHANTED_GOLDEN_CARROT: 5939.2 + POTATO: 0.33 + POISONOUS_POTATO: 0.33 + ENCHANTED_POTATO: 52.8 + ENCHANTED_POISONOUS_POTATO: 52.8 + ENCHANTED_BAKED_POTATO: 8448 + PUMPKIN: 1 + ENCHANTED_PUMPKIN: 160 + POLISHED_PUMPKIN: 25600 + MELON_SLICE: 0.2 + MELON: 1.8 + ENCHANTED_MELON_SLICE: 32 + ENCHANTED_MELON: 5120 + RED_MUSHROOM: 1 + RED_MUSHROOM_BLOCK: 9 + ENCHANTED_RED_MUSHROOM: 160 + ENCHANTED_RED_MUSHROOM_BLOCK: 5184 + BROWN_MUSHROOM: 1 + BROWN_MUSHROOM_BLOCK: 9 + ENCHANTED_BROWN_MUSHROOM: 160 + ENCHANTED_BROWN_MUSHROOM_BLOCK: 5184 + COCOA_BEANS: 0.4 + ENCHANTED_COCOA_BEAN: 64 + CACTUS: 0.5 + CACTUS_GREEN: 0.5 + ENCHANTED_CACTUS_GREEN: 80 + ENCHANTED_CACTUS: 12800 + SUGAR_CANE: 0.5 + ENCHANTED_SUGAR: 80 + ENCHANTED_PAPER: 96 + ENCHANTED_SUGAR_CANE: 12800 + NETHER_WART: 0.33 + ENCHANTED_NETHER_WART: 52.8 + MUTANT_NETHER_WART: 8448 +fuel_values: + ENCHANTED_DANDELION: 360 + ENCHANTED_POPPY: 1024 + BIOFUEL: 3000 + VOLTA: 10000 + OIL_BARREL: 10000 + CLAW_FOSSIL: 50000 + CLUBBED_FOSSIL: 50000 + FOOTPRINT_FOSSIL: 50000 + SPINE_FOSSIL: 50000 + TUSK_FOSSIL: 50000 + UGLY_FOSSIL: 50000 + WEBBED_FOSSIL: 50000 + SUNFLOWER_OIL: 20000 + COALROOT: 5000 diff --git a/configuration/skyblock/garden/copper_shop.yml b/configuration/skyblock/garden/copper_shop.yml new file mode 100644 index 0000000000..4893a23142 --- /dev/null +++ b/configuration/skyblock/garden/copper_shop.yml @@ -0,0 +1,107 @@ +categories: + farming_essentials: + - GARDEN_SCYTHE + - BUILDERS_RULER + - SUNDER_I + - GREEN_THUMB_I + - DEDICATION_I + - LOTUS_BRACELET + - LOTUS_BELT + - LOTUS_NECKLACE + - LOTUS_CLOAK + farming_tools: + - BASIC_GARDENING_HOE + - ADVANCED_GARDENING_HOE + - BASIC_GARDENING_AXE + - ADVANCED_GARDENING_AXE + - SOWLEDGE_GARDEN_CHIP + - CROPSHOT_GARDEN_CHIP + barn_skins: + - TRADING_POST_BARN_SKIN + - AUTUMN_HUT_BARN_SKIN + - BAMBOO_BARN_SKIN + - HIVE_BARN_SKIN + - CASTLE_BARN_SKIN + greenhouse_skins: [ ] + pests: [ ] +skymart: + - id: GARDEN_SCYTHE + display_name: Garden Scythe + category: farming_essentials + copper: 20 + - id: BUILDERS_RULER + display_name: Builder's Ruler + category: farming_essentials + copper: 20 + - id: SUNDER_I + display_name: Enchanted Book (Sunder I) + category: farming_essentials + copper: 10 + - id: GREEN_THUMB_I + display_name: Enchanted Book (Green Thumb I) + category: farming_essentials + copper: 1500 + - id: DEDICATION_I + display_name: Enchanted Book (Dedication I) + category: farming_essentials + copper: 250 + - id: LOTUS_BRACELET + display_name: Lotus Bracelet + category: farming_essentials + copper: 50 + - id: LOTUS_BELT + display_name: Lotus Belt + category: farming_essentials + copper: 100 + - id: LOTUS_NECKLACE + display_name: Lotus Necklace + category: farming_essentials + copper: 200 + - id: LOTUS_CLOAK + display_name: Lotus Cloak + category: farming_essentials + copper: 500 + - id: BASIC_GARDENING_HOE + display_name: Basic Gardening Hoe + category: farming_tools + copper: 5 + - id: ADVANCED_GARDENING_HOE + display_name: Advanced Gardening Hoe + category: farming_tools + copper: 25 + - id: BASIC_GARDENING_AXE + display_name: Basic Gardening Axe + category: farming_tools + copper: 5 + - id: ADVANCED_GARDENING_AXE + display_name: Advanced Gardening Axe + category: farming_tools + copper: 25 + - id: SOWLEDGE_GARDEN_CHIP + display_name: Sowledge Chip + category: farming_tools + copper: 200 + - id: CROPSHOT_GARDEN_CHIP + display_name: Cropshot Chip + category: farming_tools + copper: 500 + - id: HIVE_BARN_SKIN + display_name: Hive Barn Skin + category: barn_skins + copper: 10000 + - id: TRADING_POST_BARN_SKIN + display_name: Trading Post Barn Skin + category: barn_skins + copper: 1000 + - id: AUTUMN_HUT_BARN_SKIN + display_name: Autumn Hut Barn Skin + category: barn_skins + copper: 2000 + - id: CASTLE_BARN_SKIN + display_name: Castle Barn Skin + category: barn_skins + copper: 15000 + - id: BAMBOO_BARN_SKIN + display_name: Bamboo Barn Skin + category: barn_skins + copper: 7500 diff --git a/configuration/skyblock/garden/crop_upgrades.yml b/configuration/skyblock/garden/crop_upgrades.yml new file mode 100644 index 0000000000..ab11ca3d71 --- /dev/null +++ b/configuration/skyblock/garden/crop_upgrades.yml @@ -0,0 +1,52 @@ +fortune_per_tier: 5 +crops: + - WHEAT + - CARROT + - POTATO + - PUMPKIN + - MELON_SLICE + - CACTUS + - SUGAR_CANE + - NETHER_WART + - COCOA_BEANS + - MUSHROOM + - SUNFLOWER + - MOONFLOWER + - WILD_ROSE +tiers: + - tier: 1 + garden_level: 2 + copper: 5 + cumulative_copper: 5 + - tier: 2 + garden_level: 4 + copper: 10 + cumulative_copper: 15 + - tier: 3 + garden_level: 6 + copper: 20 + cumulative_copper: 35 + - tier: 4 + garden_level: 8 + copper: 50 + cumulative_copper: 85 + - tier: 5 + garden_level: 10 + copper: 100 + cumulative_copper: 185 + - tier: 6 + garden_level: 12 + copper: 500 + cumulative_copper: 685 + - tier: 7 + garden_level: 13 + copper: 1000 + cumulative_copper: 1685 + - tier: 8 + garden_level: 14 + copper: 2000 + cumulative_copper: 3685 + - tier: 9 + garden_level: 15 + copper: 4000 + cumulative_copper: 7685 diff --git a/configuration/skyblock/garden/greenhouse.yml b/configuration/skyblock/garden/greenhouse.yml new file mode 100644 index 0000000000..c1161cd05c --- /dev/null +++ b/configuration/skyblock/garden/greenhouse.yml @@ -0,0 +1,161 @@ +unlock_garden_level: 7 +unlock_visitor: CARPENTER +unlock_item: GREENHOUSE_BLUEPRINT +greenhouse_unlock_costs: + - { greenhouse: 1, ethereal_vines: 0 } + - { greenhouse: 2, ethereal_vines: 100 } + - { greenhouse: 3, ethereal_vines: 150 } +ethereal_vine_chances: + COMMON: 0.15 + UNCOMMON: 0.20 + RARE: 0.25 + EPIC: 0.30 + LEGENDARY: 0.40 +harvest_bounty: + - BURROWING_SPORES + - ETHEREAL_VINE + - EVERGREEN_GARDEN_CHIP + - IRIDIUM + - OVERGROWN_GRASS + - SYNTHESIS_GARDEN_CHIP +crops: + - id: WHEAT + base_yield: + WHEAT: 128 + SEEDS: 1 + growth_cycles: 8 + buffs: + - HARVEST_BOOST_20 + - id: CARROT + base_yield: + CARROT: 280 + growth_cycles: 8 + buffs: + - XP_BOOST_20 + - id: POTATO + base_yield: + POTATO: 240 + growth_cycles: 8 + buffs: + - IMMUNITY + - id: PUMPKIN + base_yield: + PUMPKIN: 108 + growth_cycles: 11 + buffs: + - BONUS_DROPS + - id: SUGAR_CANE + base_yield: + SUGAR_CANE: 160 + growth_cycles: 8 + buffs: + - IMPROVED_XP_BOOST_30 + - HARVEST_LOSS_20 + - id: MELON_SLICE + base_yield: + MELON_SLICE: 512 + growth_cycles: 11 + buffs: + - IMPROVED_WATER_RETAIN_100 + - id: CACTUS + base_yield: + CACTUS: 120 + growth_cycles: 8 + buffs: + - IMPROVED_WATER_RETAIN_100 + - HARVEST_LOSS_20 + - id: COCOA_BEANS + base_yield: + COCOA_BEANS: 160 + growth_cycles: 6 + buffs: + - IMMUNITY + - id: RED_MUSHROOM + base_yield: + RED_MUSHROOM: 121 + growth_cycles: 6 + buffs: + - IMPROVED_HARVEST_BOOST_30 + - WATER_DRAIN_30 + - id: BROWN_MUSHROOM + base_yield: + BROWN_MUSHROOM: 121 + growth_cycles: 6 + buffs: + - IMPROVED_HARVEST_BOOST_30 + - WATER_DRAIN_30 + - id: NETHER_WART + base_yield: + NETHER_WART: 240 + growth_cycles: 8 + buffs: + - IMPROVED_HARVEST_BOOST_30 + - XP_LOSS_20 + - id: SUNFLOWER + base_yield: + SUNFLOWER: 256 + growth_cycles: 15 + buffs: + - BONUS_DROPS + - id: MOONFLOWER + base_yield: + MOONFLOWER: 256 + growth_cycles: 15 + buffs: + - BONUS_DROPS + - id: WILD_ROSE + base_yield: + WILD_ROSE: 256 + growth_cycles: 15 + buffs: + - EFFECT_SPREAD +dna_milestones: + - level: 1 + analyses_required: 1 + rewards: + - BIOANALYSIS_TALISMAN + - SMALL_MUTATIONS_SACK + - SMALL_MUTATIONS_SACK_RECIPE + - HYDROCAN_TURBO_2000_SKYMART_UNLOCK + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" + - level: 2 + analyses_required: 10 + rewards: + - OVERGROWN_GRASS + - HYDROCAN_ULTRA_3000_SKYMART_UNLOCK + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" + - level: 3 + analyses_required: 15 + rewards: + - MEDIUM_MUTATIONS_SACK_RECIPE + - AQUAMASTER_X_SKYMART_UNLOCK + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" + - level: 4 + analyses_required: 20 + rewards: + - BIOANALYSIS_RING + - AQUAMASTER_HYDROMAX_SKYMART_UNLOCK + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" + - level: 5 + analyses_required: 30 + rewards: + - LARGE_MUTATIONS_SACK_RECIPE + - ESTATE_GREENHOUSE_SKIN + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" + - level: 6 + analyses_required: 40 + rewards: + - BIOANALYSIS_ARTIFACT + - "+10 Crop Growth" + - "+5 Farming Fortune" + - "+15 SkyBlock XP" diff --git a/configuration/skyblock/garden/jacobs_contests.yml b/configuration/skyblock/garden/jacobs_contests.yml new file mode 100644 index 0000000000..9aa181e422 --- /dev/null +++ b/configuration/skyblock/garden/jacobs_contests.yml @@ -0,0 +1,75 @@ +calendar: + year_length: 8928000 + day_length: 24000 + contest_interval_days: 3 + contest_duration_days: 1 +requirements: + farming_level: 10 + talk_to_jacob: true + minimum_collection: 100 +brackets: + BRONZE: + default_top_percent: 60 + goated_top_percent: 70 + SILVER: + default_top_percent: 30 + goated_top_percent: 40 + GOLD: + default_top_percent: 10 + goated_top_percent: 20 + PLATINUM: + default_top_percent: 5 + goated_top_percent: 10 + DIAMOND: + default_top_percent: 2 + goated_top_percent: 5 +include_one_player_beyond_cutoff: true +crops: + - WHEAT + - CARROT + - POTATO + - PUMPKIN + - MELON_SLICE + - MUSHROOM + - CACTUS + - SUGAR_CANE + - NETHER_WART + - COCOA_BEANS + - SUNFLOWER + - MOONFLOWER + - WILD_ROSE +rewards: + consolation: + jacobs_tickets: 1 + BRONZE: + bronze_medals: 1 + jacobs_tickets: 10 + carnival_tickets: 1 + turbo_crop_books: 1 + bits: 40 + SILVER: + silver_medals: 1 + jacobs_tickets: 15 + carnival_tickets: 1 + turbo_crop_books: 1 + bits: 50 + GOLD: + gold_medals: 1 + jacobs_tickets: 25 + carnival_tickets: 2 + turbo_crop_books: 1 + bits: 60 + PLATINUM: + gold_medals: 1 + bronze_medals: 1 + jacobs_tickets: 30 + carnival_tickets: 2 + turbo_crop_books: 1 + bits: 70 + DIAMOND: + gold_medals: 1 + silver_medals: 1 + jacobs_tickets: 35 + carnival_tickets: 3 + turbo_crop_books: 1 + bits: 80 diff --git a/configuration/skyblock/garden/levels.yml b/configuration/skyblock/garden/levels.yml new file mode 100644 index 0000000000..9765011aa3 --- /dev/null +++ b/configuration/skyblock/garden/levels.yml @@ -0,0 +1,216 @@ +max_level: 15 +crop_growth_per_level_percent: 10 +skyblock_xp_per_level: 10 +levels: + - level: 1 + visitor_unlocks: 46 + crop_unlocks: + - WHEAT + rewards: + - { type: VISITOR_UNLOCKS, amount: 46 } + - { type: CROP_UNLOCK, key: WHEAT } + - level: 2 + visitor_unlocks: 9 + crop_unlocks: + - CARROT + crop_upgrade_tier: 1 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 9 } + - { type: CROP_UNLOCK, key: CARROT } + - { type: CROP_UPGRADE_TIER, amount: 1 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 3 + visitor_unlocks: 13 + crop_unlocks: + - POTATO + barn_skin_unlocks: + - spruce + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 13 } + - { type: CROP_UNLOCK, key: POTATO } + - { type: BARN_SKIN_UNLOCK, key: spruce } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 4 + visitor_unlocks: 5 + crop_unlocks: + - PUMPKIN + crop_upgrade_tier: 2 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 5 } + - { type: CROP_UNLOCK, key: PUMPKIN } + - { type: CROP_UPGRADE_TIER, amount: 2 } + - { type: PROFILE_FLAG, key: GEARING_UP_QUEST, display: Gearing Up Quest } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 5 + visitor_unlocks: 5 + crop_unlocks: + - SUGAR_CANE + pest_unlocks: + - FLY + - CRICKET + - LOCUST + - RAT + - MOSQUITO + - FIELD_MOUSE + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 5 } + - { type: CROP_UNLOCK, key: SUGAR_CANE } + - { type: PEST_UNLOCK, key: FLY } + - { type: PEST_UNLOCK, key: CRICKET } + - { type: PEST_UNLOCK, key: LOCUST } + - { type: PEST_UNLOCK, key: RAT } + - { type: PEST_UNLOCK, key: FIELD_MOUSE } + - { type: PEST_UNLOCK, key: MOSQUITO } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 6 + visitor_unlocks: 6 + crop_unlocks: + - MELON_SLICE + pest_unlocks: + - EARTHWORM + crop_upgrade_tier: 3 + barn_skin_unlocks: + - sunny + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 6 } + - { type: CROP_UNLOCK, key: MELON_SLICE } + - { type: PEST_UNLOCK, key: EARTHWORM } + - { type: CROP_UPGRADE_TIER, amount: 3 } + - { type: BARN_SKIN_UNLOCK, key: sunny } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 7 + visitor_unlocks: 10 + crop_unlocks: + - CACTUS + pest_unlocks: + - MITE + greenhouse_unlocked: true + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 10 } + - { type: CROP_UNLOCK, key: CACTUS } + - { type: PEST_UNLOCK, key: MITE } + - { type: GREENHOUSE_UNLOCK } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 8 + visitor_unlocks: 9 + crop_unlocks: + - COCOA_BEANS + pest_unlocks: + - MOTH + crop_upgrade_tier: 4 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 9 } + - { type: CROP_UNLOCK, key: COCOA_BEANS } + - { type: PEST_UNLOCK, key: MOTH } + - { type: CROP_UPGRADE_TIER, amount: 4 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 9 + visitor_unlocks: 8 + crop_unlocks: + - MUSHROOM + pest_unlocks: + - SLUG + barn_skin_unlocks: + - red + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 8 } + - { type: CROP_UNLOCK, key: MUSHROOM } + - { type: PEST_UNLOCK, key: SLUG } + - { type: BARN_SKIN_UNLOCK, key: red } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 10 + visitor_unlocks: 9 + crop_unlocks: + - NETHER_WART + pest_unlocks: + - BEETLE + crop_upgrade_tier: 5 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 9 } + - { type: CROP_UNLOCK, key: NETHER_WART } + - { type: PEST_UNLOCK, key: BEETLE } + - { type: CROP_UPGRADE_TIER, amount: 5 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 11 + visitor_unlocks: 3 + crop_unlocks: + - SUNFLOWER + - MOONFLOWER + pest_unlocks: + - FIREFLY + - DRAGONFLY + barn_skin_unlocks: + - cabin + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 3 } + - { type: CROP_UNLOCK, key: SUNFLOWER } + - { type: CROP_UNLOCK, key: MOONFLOWER } + - { type: PEST_UNLOCK, key: FIREFLY } + - { type: PEST_UNLOCK, key: DRAGONFLY } + - { type: BARN_SKIN_UNLOCK, key: cabin } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 12 + visitor_unlocks: 9 + crop_unlocks: + - WILD_ROSE + pest_unlocks: + - PRAYING_MANTIS + crop_upgrade_tier: 6 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 9 } + - { type: CROP_UNLOCK, key: WILD_ROSE } + - { type: PEST_UNLOCK, key: PRAYING_MANTIS } + - { type: CROP_UPGRADE_TIER, amount: 6 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 13 + visitor_unlocks: 2 + crop_upgrade_tier: 7 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 2 } + - { type: CROP_UPGRADE_TIER, amount: 7 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 14 + visitor_unlocks: 1 + crop_upgrade_tier: 8 + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 1 } + - { type: CROP_UPGRADE_TIER, amount: 8 } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } + - level: 15 + visitor_unlocks: 2 + crop_upgrade_tier: 9 + barn_skin_unlocks: + - mansion_heights + skyblock_xp: 10 + rewards: + - { type: VISITOR_UNLOCKS, amount: 2 } + - { type: CROP_UPGRADE_TIER, amount: 9 } + - { type: BARN_SKIN_UNLOCK, key: mansion_heights } + - { type: SKYBLOCK_XP, amount: 10 } + - { type: CROP_GROWTH, amount: 10 } diff --git a/configuration/skyblock/garden/milestones.yml b/configuration/skyblock/garden/milestones.yml new file mode 100644 index 0000000000..2860e950b7 --- /dev/null +++ b/configuration/skyblock/garden/milestones.yml @@ -0,0 +1,185 @@ +crop_groups: + small_30: + thresholds: [ 30, 50, 80, 200, 350, 700, 1500, 2500, 3500, 5000, 6500, 8000, 10000, 20000, 35000, 50000, 75000, 100000, 175000, 250000, 325000, 400000, 500000, 650000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000, 800000 ] + large_60: + thresholds: [ 60, 100, 160, 400, 700, 1400, 3000, 5000, 7000, 10000, 13000, 16000, 20000, 40000, 70000, 100000, 150000, 200000, 350000, 500000, 650000, 800000, 1000000, 1300000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000, 1600000 ] + large_90: + thresholds: [ 90, 150, 250, 600, 1000, 2000, 4000, 6000, 9000, 12000, 15000, 18000, 22000, 44000, 77000, 110000, 165000, 220000, 385000, 550000, 715000, 880000, 1100000, 1430000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000, 2400000 ] + large_100: + thresholds: [ 100, 150, 250, 500, 1000, 2000, 4000, 6000, 9000, 12000, 15000, 18000, 22000, 44000, 77000, 110000, 165000, 220000, 385000, 550000, 715000, 880000, 1100000, 1430000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000, 1760000 ] + large_150: + thresholds: [ 150, 250, 400, 1000, 1750, 3500, 7500, 12500, 17500, 25000, 32500, 40000, 50000, 100000, 175000, 250000, 375000, 500000, 875000, 1250000, 1625000, 2000000, 2500000, 3250000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000, 4000000 ] + +crop_milestones: + - id: WHEAT + display_name: Wheat + icon: WHEAT + group: small_30 + - id: CARROT + display_name: Carrot + icon: CARROT + group: large_100 + - id: POTATO + display_name: Potato + icon: POTATO + group: large_100 + - id: PUMPKIN + display_name: Pumpkin + icon: PUMPKIN + group: small_30 + - id: SUGAR_CANE + display_name: Sugar Cane + icon: SUGAR_CANE + group: large_60 + - id: MELON_SLICE + display_name: Melon Slice + icon: MELON_SLICE + group: large_150 + - id: CACTUS + display_name: Cactus + icon: CACTUS + group: large_60 + - id: COCOA_BEANS + display_name: Cocoa Beans + icon: COCOA_BEANS + group: large_90 + - id: MUSHROOM + display_name: Mushroom + icon: RED_MUSHROOM + group: small_30 + - id: NETHER_WART + display_name: Nether Wart + icon: NETHER_WART + group: large_90 + - id: MOONFLOWER + display_name: Moonflower + icon: MOONFLOWER + group: small_30 + - id: SUNFLOWER + display_name: Sunflower + icon: SUNFLOWER + group: small_30 + - id: WILD_ROSE + display_name: Wild Rose + icon: WILD_ROSE + group: large_60 + +crop_reward_tiers: + - { tier: 1, amount: 1, farming_xp: 100, garden_xp: 10, skyblock_xp: 1 } + - { tier: 2, amount: 1, farming_xp: 250, garden_xp: 20, skyblock_xp: 1 } + - { tier: 3, amount: 1, farming_xp: 500, garden_xp: 30, skyblock_xp: 1 } + - { tier: 4, amount: 1, farming_xp: 1000, garden_xp: 40, skyblock_xp: 1 } + - { tier: 5, amount: 1, farming_xp: 2500, garden_xp: 50, skyblock_xp: 1 } + - { tier: 6, amount: 1, farming_xp: 5000, garden_xp: 60, skyblock_xp: 1 } + - { tier: 7, amount: 1, farming_xp: 10000, garden_xp: 70, skyblock_xp: 1 } + - { tier: 8, amount: 1, farming_xp: 15000, garden_xp: 80, skyblock_xp: 1 } + - { tier: 9, amount: 1, farming_xp: 20000, garden_xp: 90, skyblock_xp: 1 } + - { tier: 10, amount: 1, farming_xp: 30000, garden_xp: 100, skyblock_xp: 1 } + - { tier: 11, amount: 1, farming_xp: 35000, garden_xp: 110, skyblock_xp: 1 } + - { tier: 12, amount: 1, farming_xp: 40000, garden_xp: 120, skyblock_xp: 1 } + - { tier: 13, amount: 1, farming_xp: 45000, garden_xp: 130, skyblock_xp: 1 } + - { tier: 14, amount: 1, farming_xp: 50000, garden_xp: 140, skyblock_xp: 1 } + - { tier: 15, amount: 1, farming_xp: 50000, garden_xp: 150, skyblock_xp: 1 } + - { tier: 16, amount: 1, farming_xp: 50000, garden_xp: 160, skyblock_xp: 1 } + - { tier: 17, amount: 1, farming_xp: 50000, garden_xp: 170, skyblock_xp: 1 } + - { tier: 18, amount: 1, farming_xp: 50000, garden_xp: 180, skyblock_xp: 1 } + - { tier: 19, amount: 1, farming_xp: 50000, garden_xp: 190, skyblock_xp: 1 } + - { tier: 20, amount: 1, farming_xp: 50000, garden_xp: 200, skyblock_xp: 1 } + - { tier: 21, amount: 1, farming_xp: 50000, garden_xp: 210, skyblock_xp: 1 } + - { tier: 22, amount: 1, farming_xp: 50000, garden_xp: 220, skyblock_xp: 1 } + - { tier: 23, amount: 1, farming_xp: 50000, garden_xp: 230, skyblock_xp: 1 } + - { tier: 24, amount: 1, farming_xp: 50000, garden_xp: 240, skyblock_xp: 1 } + - { tier: 25, amount: 1, farming_xp: 50000, garden_xp: 250, skyblock_xp: 1 } + - { tier: 26, amount: 1, farming_xp: 50000, garden_xp: 260, skyblock_xp: 1 } + - { tier: 27, amount: 1, farming_xp: 50000, garden_xp: 270, skyblock_xp: 1 } + - { tier: 28, amount: 1, farming_xp: 50000, garden_xp: 280, skyblock_xp: 1 } + - { tier: 29, amount: 1, farming_xp: 50000, garden_xp: 290, skyblock_xp: 1 } + - { tier: 30, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 31, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 32, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 33, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 34, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 35, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 36, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 37, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 38, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 39, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 40, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 41, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 42, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 43, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 44, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 45, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + - { tier: 46, amount: 1, farming_xp: 50000, garden_xp: 300, skyblock_xp: 1 } + +visitor_tracks: + unique_served: + display_name: Unique Visitors Served + icon: BOOK + tiers: + - { tier: 1, amount: 1, farming_xp: 500, garden_xp: 30, skyblock_xp: 3 } + - { tier: 2, amount: 4, farming_xp: 1000, garden_xp: 60, skyblock_xp: 3 } + - { tier: 3, amount: 5, farming_xp: 2000, garden_xp: 90, skyblock_xp: 3 } + - { tier: 4, amount: 10, farming_xp: 3000, garden_xp: 120, skyblock_xp: 3 } + - { tier: 5, amount: 10, farming_xp: 4000, garden_xp: 150, skyblock_xp: 3 } + - { tier: 6, amount: 10, farming_xp: 5000, garden_xp: 180, skyblock_xp: 3 } + - { tier: 7, amount: 10, farming_xp: 6000, garden_xp: 210, skyblock_xp: 3 } + - { tier: 8, amount: 10, farming_xp: 7000, garden_xp: 240, skyblock_xp: 3 } + - { tier: 9, amount: 10, farming_xp: 8000, garden_xp: 270, skyblock_xp: 3 } + - { tier: 10, amount: 10, farming_xp: 9000, garden_xp: 300, skyblock_xp: 3 } + - { tier: 11, amount: 10, farming_xp: 10000, garden_xp: 330, skyblock_xp: 3 } + - { tier: 12, amount: 10, farming_xp: 10000, garden_xp: 360, skyblock_xp: 3 } + - { tier: 13, amount: 10, farming_xp: 12000, garden_xp: 390, skyblock_xp: 3 } + - { tier: 14, amount: 10, farming_xp: 14000, garden_xp: 420, skyblock_xp: 3 } + - { tier: 15, amount: 10, farming_xp: 16000, garden_xp: 450, skyblock_xp: 3 } + - { tier: 16, amount: 10, farming_xp: 18000, garden_xp: 480, skyblock_xp: 3 } + - { tier: 17, amount: 10, farming_xp: 20000, garden_xp: 510, skyblock_xp: 3 } + - { tier: 18, amount: 10, farming_xp: 22000, garden_xp: 540, skyblock_xp: 3 } + - { tier: 19, amount: 10, farming_xp: 24000, garden_xp: 570, skyblock_xp: 3 } + - { tier: 20, amount: 10, farming_xp: 26000, garden_xp: 600, skyblock_xp: 3 } + - { tier: 21, amount: 10, farming_xp: 28000, garden_xp: 630, skyblock_xp: 3 } + - { tier: 22, amount: 10, farming_xp: 30000, garden_xp: 660, skyblock_xp: 3 } + - { tier: 23, amount: 10, farming_xp: 32000, garden_xp: 690, skyblock_xp: 3 } + - { tier: 24, amount: 10, farming_xp: 34000, garden_xp: 720, skyblock_xp: 3 } + - { tier: 25, amount: 10, farming_xp: 36000, garden_xp: 750, skyblock_xp: 3 } + - { tier: 26, amount: 10, farming_xp: 38000, garden_xp: 780, skyblock_xp: 3 } + - { tier: 27, amount: 10, farming_xp: 40000, garden_xp: 810, skyblock_xp: 3 } + - { tier: 28, amount: 10, farming_xp: 42000, garden_xp: 840, skyblock_xp: 3 } + - { tier: 29, amount: 10, farming_xp: 44000, garden_xp: 870, skyblock_xp: 3 } + - { tier: 30, amount: 10, farming_xp: 46000, garden_xp: 900, skyblock_xp: 3 } + - { tier: 31, amount: 10, farming_xp: 48000, garden_xp: 930, skyblock_xp: 3 } + - { tier: 32, amount: 10, farming_xp: 50000, garden_xp: 960, skyblock_xp: 3 } + offers_accepted: + display_name: Offers Accepted + icon: WHEAT + tiers: + - { tier: 1, amount: 1, farming_xp: 500, garden_xp: 30, skyblock_xp: 3 } + - { tier: 2, amount: 4, farming_xp: 1000, garden_xp: 60, skyblock_xp: 3 } + - { tier: 3, amount: 5, farming_xp: 2000, garden_xp: 90, skyblock_xp: 3 } + - { tier: 4, amount: 10, farming_xp: 3000, garden_xp: 120, skyblock_xp: 3 } + - { tier: 5, amount: 10, farming_xp: 5000, garden_xp: 150, skyblock_xp: 3 } + - { tier: 6, amount: 20, farming_xp: 7500, garden_xp: 180, skyblock_xp: 3 } + - { tier: 7, amount: 25, farming_xp: 15000, garden_xp: 210, skyblock_xp: 3 } + - { tier: 8, amount: 75, farming_xp: 20000, garden_xp: 240, skyblock_xp: 3 } + - { tier: 9, amount: 100, farming_xp: 25000, garden_xp: 270, skyblock_xp: 3 } + - { tier: 10, amount: 150, farming_xp: 30000, garden_xp: 300, skyblock_xp: 3 } + - { tier: 11, amount: 250, farming_xp: 30000, garden_xp: 330, skyblock_xp: 3 } + - { tier: 12, amount: 350, farming_xp: 30000, garden_xp: 360, skyblock_xp: 3 } + - { tier: 13, amount: 500, farming_xp: 30000, garden_xp: 390, skyblock_xp: 3 } + - { tier: 14, amount: 500, farming_xp: 30000, garden_xp: 420, skyblock_xp: 3 } + - { tier: 15, amount: 500, farming_xp: 30000, garden_xp: 450, skyblock_xp: 3 } + - { tier: 16, amount: 500, farming_xp: 30000, garden_xp: 480, skyblock_xp: 3 } + - { tier: 17, amount: 500, farming_xp: 30000, garden_xp: 510, skyblock_xp: 3 } + - { tier: 18, amount: 500, farming_xp: 30000, garden_xp: 540, skyblock_xp: 3 } + - { tier: 19, amount: 500, farming_xp: 30000, garden_xp: 570, skyblock_xp: 3 } + - { tier: 20, amount: 500, farming_xp: 30000, garden_xp: 600, skyblock_xp: 3 } + - { tier: 21, amount: 500, farming_xp: 30000, garden_xp: 630, skyblock_xp: 3 } + - { tier: 22, amount: 500, farming_xp: 30000, garden_xp: 660, skyblock_xp: 3 } + - { tier: 23, amount: 500, farming_xp: 30000, garden_xp: 690, skyblock_xp: 3 } + - { tier: 24, amount: 500, farming_xp: 30000, garden_xp: 720, skyblock_xp: 3 } + - { tier: 25, amount: 500, farming_xp: 30000, garden_xp: 750, skyblock_xp: 3 } + - { tier: 26, amount: 500, farming_xp: 30000, garden_xp: 780, skyblock_xp: 3 } + - { tier: 27, amount: 500, farming_xp: 30000, garden_xp: 810, skyblock_xp: 3 } + - { tier: 28, amount: 500, farming_xp: 30000, garden_xp: 840, skyblock_xp: 3 } + - { tier: 29, amount: 500, farming_xp: 30000, garden_xp: 870, skyblock_xp: 3 } + - { tier: 30, amount: 500, farming_xp: 30000, garden_xp: 900, skyblock_xp: 3 } diff --git a/configuration/skyblock/garden/mutations.yml b/configuration/skyblock/garden/mutations.yml new file mode 100644 index 0000000000..1a7eb5195f --- /dev/null +++ b/configuration/skyblock/garden/mutations.yml @@ -0,0 +1,41 @@ +mutations: + - { id: ASHWREATH, rarity: COMMON, size: "1x1", growth_surface: SOUL_SAND, spreading: "2x Nether Wart / 2x Fire", drops: "720 Nether Wart", effects: [ IMPROVED_HARVEST_BOOST, XP_LOSS ] } + - { id: CHOCONUT, display_name: Choconut, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "2x Cocoa Beans", drops: "400 Cocoa Beans", effects: [ IMMUNITY ] } + - { id: DUSTGRAIN, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "2x Wheat", drops: "200 Wheat", effects: [ HARVEST_BOOST ] } + - { id: GLOOMGOURD, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "1x Pumpkin / 1x Melon", drops: "60 Pumpkin / 280 Melon Slice", effects: [ WATER_RETAIN, BONUS_DROPS ] } + - { id: LONELILY, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "0 adjacent crops", drops: "600 Potato / 700 Carrot / 340 Pumpkin", effects: [ BONUS_DROPS ] } + - { id: SCOURROOT, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "1x Potato / 1x Carrot", drops: "210 Potato / 245 Carrot", effects: [ IMMUNITY, XP_BOOST ] } + - { id: SHADEVINE, rarity: COMMON, size: "1x1", growth_surface: FARMLAND, spreading: "1x Cactus / 1x Sugar Cane", drops: "135 Cactus / 180 Sugar Cane", effects: [ IMPROVED_WATER_RETAIN, IMPROVED_XP_BOOST, HARVEST_LOSS ] } + - { id: VEILSHROOM, rarity: COMMON, size: "1x1", growth_surface: MYCELIUM, spreading: "1x Red Mushroom / 1x Brown Mushroom", drops: "66 Brown Mushroom / 66 Red Mushroom", effects: [ IMPROVED_HARVEST_BOOST, WATER_DRAIN ] } + - { id: WITHERBLOOM, rarity: COMMON, size: "1x1", growth_surface: SOUL_SAND, spreading: "4x Dead Plant", drops: "600 Wild Rose", effects: [ EFFECT_SPREAD ] } + - { id: CHOCOBERRY, rarity: UNCOMMON, size: "1x1", growth_surface: FARMLAND, spreading: "6x Choconut / 2x Gloomgourd", drops: "400 Cocoa Beans / 170 Pumpkin / 1600 Melon Slice", effects: [ WATER_RETAIN ] } + - { id: CINDERSHADE, rarity: UNCOMMON, size: "1x1", growth_surface: SOUL_SAND, spreading: "4x Ashwreath / 4x Witherbloom", drops: "1200 Nether Wart / 800 Wild Rose", effects: [ EFFECT_SPREAD, IMPROVED_HARVEST_BOOST, XP_LOSS ] } + - { id: COALROOT, rarity: UNCOMMON, size: "1x1", growth_surface: FARMLAND, spreading: "5x Ashwreath / 3x Scourroot", drops: "600 Potato / 1400 Carrot / 600 Nether Wart", effects: [ XP_BOOST ] } + - { id: CREAMBLOOM, rarity: UNCOMMON, size: "1x1", growth_surface: FARMLAND, spreading: "8x Choconut", drops: "1600 Cocoa Beans", effects: [ IMMUNITY ] } + - { id: DUSKBLOOM, rarity: UNCOMMON, size: "1x1", growth_surface: FARMLAND, spreading: "2x Moonflower / 2x Shadevine / 2x Sunflower / 2x Dustgrain", drops: "533 Moonflower / 533 Sunflower / 267 Wheat", effects: [ BONUS_DROPS ] } + - { id: THORNSHADE, rarity: UNCOMMON, size: "1x1", growth_surface: FARMLAND, spreading: "4x Wild Rose / 4x Veilshroom", drops: "190 Brown Mushroom / 190 Red Mushroom / 800 Wild Rose", effects: [ EFFECT_SPREAD ] } + - { id: BLASTBERRY, rarity: RARE, size: "1x1", growth_surface: SAND, spreading: "5x Chocoberry / 3x Ashwreath", drops: "1200 Cocoa Beans / 1800 Nether Wart", effects: [ IMMUNITY, IMPROVED_HARVEST_BOOST, XP_LOSS ] } + - { id: CHEESEBITE, rarity: RARE, size: "1x1", growth_surface: FARMLAND, spreading: "4x Creambloom / 4x Fermento", drops: "190 Brown Mushroom / 600 Cactus / 800 Sugar Cane / 190 Red Mushroom", effects: [ IMPROVED_WATER_RETAIN ] } + - { id: CHLORONITE, rarity: RARE, size: "1x1", growth_surface: FARMLAND, spreading: "6x Coalroot / 2x Thornshade", drops: "95 Brown Mushroom / 600 Potato / 700 Carrot / 95 Red Mushroom / 400 Wild Rose", effects: [ IMMUNITY ] } + - { id: DO_NOT_EAT_SHROOM, rarity: RARE, size: "1x1", growth_surface: FARMLAND, spreading: "4x Veilshroom / 4x Scourroot", drops: "380 Brown Mushroom / 1200 Potato / 1400 Carrot / 380 Red Mushroom", effects: [ IMPROVED_HARVEST_BOOST, WATER_DRAIN ] } + - { id: FLESHTRAP, rarity: RARE, size: "1x1", growth_surface: FARMLAND, spreading: "4x Cindershade / 4x Lonelily", drops: "1200 Potato / 1400 Carrot / 680 Pumpkin", effects: [ BONUS_DROPS ] } + - { id: MAGIC_JELLYBEAN, rarity: RARE, size: "1x1", growth_surface: SAND, spreading: "5x Sugar Cane / 3x Duskbloom", drops: "600 Moonflower / 600 Sunflower / 1200 Sugar Cane", effects: [ IMPROVED_XP_BOOST, HARVEST_LOSS ] } + - { id: NOCTILUME, rarity: RARE, size: "2x2", growth_surface: FARMLAND, spreading: "6x Duskbloom / 6x Lonelily", drops: "1200 Cactus / 1600 Wild Rose", effects: [ EFFECT_SPREAD, IMPROVED_WATER_RETAIN, HARVEST_LOSS ] } + - { id: SNOOZLING, rarity: RARE, size: "3x3", growth_surface: FARMLAND, spreading: "4x Creambloom / 3x Dustgrain / 3x Witherbloom / 3x Duskbloom / 3x Thornshade", drops: "800 Moonflower / 800 Sunflower / 600 Cactus / 800 Sugar Cane", effects: [ BONUS_DROPS ] } + - { id: SOGGYBUD, rarity: RARE, size: "1x1", growth_surface: FARMLAND, spreading: "2x Melon / 2x Gloomgourd", drops: "3200 Melon Slice", effects: [ WATER_RETAIN ] } + - { id: CHORUS_FRUIT, rarity: EPIC, size: "1x1", growth_surface: END_STONE, spreading: "5x Chloronite / 3x Magic Jellybean", drops: "1500 Potato / 1700 Carrot / 2000 Sugar Cane", effects: [ IMPROVED_XP_BOOST, HARVEST_LOSS ] } + - { id: PLANTBOY_ADVANCE, display_name: "PlantBoy Advance", rarity: EPIC, size: "2x2", growth_surface: FARMLAND, spreading: "6x Snoozling / 6x Thunderling", drops: "1200 Moonflower / 1200 Sunflower / 122 Wheat", effects: [ HARVEST_BOOST ] } + - { id: PUFFERCLOUD, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "2x Snoozling / 6x Do-not-eat-shroom", drops: "665 Brown Mushroom / 1400 Moonflower / 1400 Sunflower / 665 Red Mushroom", effects: [ IMPROVED_HARVEST_BOOST, WATER_DRAIN ] } + - { id: SHELLFRUIT, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "Explode a Turtlellini with a Blastberry", drops: "400 Cocoa Beans / 800 Melon Slice", effects: [ WATER_RETAIN, IMMUNITY ] } + - { id: STARTLEVINE, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "4x Blastberry / 4x Cheesebite", drops: "1500 Cactus / 2000 Sugar Cane", effects: [ IMPROVED_WATER_RETAIN, IMPROVED_XP_BOOST, HARVEST_LOSS ] } + - { id: STOPLIGHT_PETAL, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "4x Snoozling / 4x Noctilume", drops: "2400 Cactus / 3200 Wild Rose", effects: [ EFFECT_SPREAD, IMPROVED_WATER_RETAIN, HARVEST_LOSS ] } + - { id: THUNDERLING, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "5x Soggybud / 3x Noctilume", drops: "900 Cactus / 2400 Melon Slice / 2400 Wild Rose", effects: [ EFFECT_SPREAD ] } + - { id: TURTLELLINI, rarity: EPIC, size: "1x1", growth_surface: FARMLAND, spreading: "4x Soggybud / 4x Choconut", drops: "None", effects: [ WATER_RETAIN, IMMUNITY ] } + - { id: ZOMBUD, rarity: EPIC, size: "1x1", growth_surface: SOUL_SAND, spreading: "4x Dead Plant / 2x Cindershade / 2x Fleshtrap", drops: "1190 Pumpkin / 2800 Wild Rose", effects: [ EFFECT_SPREAD, BONUS_DROPS ] } + - { id: ALL_IN_ALOE, display_name: "All-in Aloe", rarity: LEGENDARY, size: "1x1", growth_surface: SAND, spreading: "6x Magic Jellybean / 2x PlantBoy Advance", drops: "400 Sunflower / 200 Wheat", effects: [ HARVEST_BOOST ] } + - { id: DEVOURER, rarity: LEGENDARY, size: "1x1", growth_surface: FARMLAND, spreading: "4x Puffercloud / 4x Zombud", drops: "1700 Pumpkin / 950 Brown Mushroom / 950 Red Mushroom", effects: [ BONUS_DROPS, IMPROVED_HARVEST_BOOST, WATER_DRAIN ] } + - { id: GLASSCORN, rarity: LEGENDARY, size: "2x2", growth_surface: SAND, spreading: "6x Startlevine / 6x Chloronite", drops: "2400 Cactus / 4800 Potato", effects: [ IMMUNITY, IMPROVED_WATER_RETAIN, HARVEST_LOSS ] } + - { id: GODSEED, rarity: LEGENDARY, size: "3x3", growth_surface: FARMLAND, spreading: "All positive crop effects", drops: "Mixed all-crop bundle", effects: [ IMPROVED_HARVEST_BOOST, IMPROVED_WATER_RETAIN, IMPROVED_XP_BOOST, IMMUNITY, BONUS_DROPS, EFFECT_SPREAD ] } + - { id: JERRYFLOWER, rarity: LEGENDARY, size: "1x1", growth_surface: FARMLAND, spreading: "Grow the Jerryseed", drops: "None", effects: [ ] } + - { id: PHANTOMLEAF, rarity: LEGENDARY, size: "1x1", growth_surface: SOUL_SAND, spreading: "4x Chorus Fruit / 4x Shellfruit", drops: "4800 Potato / 5600 Carrot", effects: [ XP_BOOST, IMMUNITY ] } + - { id: TIMESTALK, rarity: LEGENDARY, size: "1x1", growth_surface: END_STONE, spreading: "4x Stoplight Petal / 2x Chorus Fruit / 2x Shellfruit", drops: "3000 Cactus / 4000 Sugar Cane", effects: [ IMPROVED_WATER_RETAIN, IMPROVED_XP_BOOST, HARVEST_LOSS ] } diff --git a/configuration/skyblock/garden/npc_anchors.yml b/configuration/skyblock/garden/npc_anchors.yml new file mode 100644 index 0000000000..1d256a5025 --- /dev/null +++ b/configuration/skyblock/garden/npc_anchors.yml @@ -0,0 +1,115 @@ +skins: + default: + npcs: + sam: { enabled: true, x: -8.0, y: 71.0, z: -7.0, yaw: 0, pitch: 0 } + anita: { enabled: true, x: -23.0, y: 71.0, z: -15.0, yaw: 0, pitch: 0 } + pamela: { enabled: true, x: -24.0, y: 71.0, z: -15.0, yaw: 0, pitch: 0 } + jacob: { enabled: true, x: -21.0, y: 71.0, z: -15.0, yaw: 0, pitch: 0 } + jeff: { enabled: true, x: 0.0, y: 72.0, z: -29.0, yaw: 0, pitch: 0 } + phillip: { enabled: true, x: -25.5, y: 71.0, z: -14.0, yaw: 0, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 1 } + composter: { enabled: true, x: -11.0, y: 72, z: -28.0, yaw: 180, pitch: 0, offsetY: 2 } + visitor_logbook: { enabled: true, x: 7.5, y: 73.0, z: -22.5, yaw: 180, pitch: 0, offsetY: 0.5 } + visitor_slots: + slot_1: { enabled: true, x: 5.0, y: 72.0, z: -18.0, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 5.0, y: 72.0, z: -15.0, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 7.0, y: 72.0, z: -13.0, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 9.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 12.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + armor_stands: { } + spruce: + npcs: + sam: { enabled: true, x: -8.0, y: 71.0, z: -7.0, yaw: 180, pitch: 0 } + anita: { enabled: true, x: -23.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + pamela: { enabled: true, x: -24.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jacob: { enabled: true, x: -21.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jeff: { enabled: true, x: 0.0, y: 72.0, z: -29.0, yaw: 180, pitch: 0 } + phillip: { enabled: true, x: -26.0, y: 71.0, z: -14.0, yaw: 90, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 0.5 } + composter: { enabled: true, x: -11.0, y: 71.0, z: -28.0, yaw: 180, pitch: 0, offsetY: 3 } + visitor_logbook: { enabled: true, x: 7.5, y: 73.0, z: -22.5, yaw: 180, pitch: 0 } + visitor_slots: + slot_1: { enabled: true, x: 5.0, y: 72.0, z: -18.0, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 5.0, y: 72.0, z: -15.0, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 7.0, y: 72.0, z: -13.0, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 9.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 12.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + armor_stands: { } + red: + npcs: + sam: { enabled: true, x: -8.0, y: 71.0, z: -7.0, yaw: 180, pitch: 0 } + anita: { enabled: true, x: -23.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + pamela: { enabled: true, x: -24.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jacob: { enabled: true, x: -21.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jeff: { enabled: true, x: 0.0, y: 72.0, z: -29.0, yaw: 180, pitch: 0 } + phillip: { enabled: true, x: -26.0, y: 71.0, z: -14.0, yaw: 90, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 0.5 } + composter: { enabled: true, x: -11.0, y: 71.0, z: -28.0, yaw: 180, pitch: 0, offsetY: 3 } + visitor_logbook: { enabled: true, x: 7.5, y: 73.0, z: -22.5, yaw: 180, pitch: 0 } + visitor_slots: + slot_1: { enabled: true, x: 5.0, y: 72.0, z: -18.0, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 5.0, y: 72.0, z: -15.0, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 7.0, y: 72.0, z: -13.0, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 9.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 12.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + armor_stands: { } + sunny: + npcs: + sam: { enabled: true, x: -8.0, y: 71.0, z: -7.0, yaw: 180, pitch: 0 } + anita: { enabled: true, x: -23.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + pamela: { enabled: true, x: -24.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jacob: { enabled: true, x: -21.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jeff: { enabled: true, x: 0.0, y: 72.0, z: -29.0, yaw: 180, pitch: 0 } + phillip: { enabled: true, x: -26.0, y: 71.0, z: -14.0, yaw: 90, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 0.5 } + composter: { enabled: true, x: -11.0, y: 71.0, z: -28.0, yaw: 180, pitch: 0, offsetY: 3 } + visitor_logbook: { enabled: true, x: 7.5, y: 73.0, z: -22.5, yaw: 180, pitch: 0 } + visitor_slots: + slot_1: { enabled: true, x: 5.0, y: 72.0, z: -18.0, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 5.0, y: 72.0, z: -15.0, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 7.0, y: 72.0, z: -13.0, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 9.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 12.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + armor_stands: { } + cabin: + npcs: + sam: { enabled: true, x: -8.0, y: 71.0, z: -7.0, yaw: 180, pitch: 0 } + anita: { enabled: true, x: -23.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + pamela: { enabled: true, x: -24.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jacob: { enabled: true, x: -21.0, y: 71.0, z: -15.0, yaw: 90, pitch: 0 } + jeff: { enabled: true, x: 0.0, y: 72.0, z: -29.0, yaw: 180, pitch: 0 } + phillip: { enabled: true, x: -26.0, y: 71.0, z: -14.0, yaw: 90, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 0.5 } + composter: { enabled: true, x: -11.0, y: 71.0, z: -28.0, yaw: 180, pitch: 0, offsetY: 3 } + visitor_logbook: { enabled: true, x: 7.5, y: 73.0, z: -22.5, yaw: 180, pitch: 0 } + visitor_slots: + slot_1: { enabled: true, x: 5.0, y: 72.0, z: -18.0, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 5.0, y: 72.0, z: -15.0, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 7.0, y: 72.0, z: -13.0, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 9.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 12.0, y: 71.0, z: -11.0, yaw: 180, pitch: 0 } + armor_stands: { } + chocolate: + npcs: + sam: { enabled: true, x: -7.5, y: 71.0, z: -6.5, yaw: 0, pitch: 0 } + anita: { enabled: true, x: -21.5, y: 71.0, z: -11.5, yaw: 0, pitch: 0 } + jacob: { enabled: true, x: -19.5, y: 71.0, z: -11.5, yaw: 0, pitch: 0 } + jeff: { enabled: true, x: -9.0, y: 80.0, z: -15.0, yaw: 0, pitch: 0 } + phillip: { enabled: true, x: -25.5, y: 71.0, z: -10.5, yaw: 0, pitch: 0 } + pamela: { enabled: true, x: -23.5, y: 71.0, z: -10.5, yaw: 0, pitch: 0 } + interactions: + desk: { enabled: true, x: -5.5, y: 72, z: -24.5, yaw: 180, pitch: 0, offsetY: 0.5 } + composter: { enabled: true, x: -11.0, y: 71.0, z: -28.0, yaw: 180, pitch: 0, offsetY: 3 } + visitor_logbook: { enabled: true, x: 24.5, y: 73.0, z: -17.5, yaw: 180, pitch: 0 } + visitor_slots: + slot_1: { enabled: true, x: 24.5, y: 72.0, z: -15.5, yaw: 180, pitch: 0 } + slot_2: { enabled: true, x: 24.5, y: 72.0, z: -12.5, yaw: 180, pitch: 0 } + slot_3: { enabled: true, x: 27.5, y: 71.0, z: -9.5, yaw: 180, pitch: 0 } + slot_4: { enabled: true, x: 30.5, y: 71.0, z: -9.5, yaw: 180, pitch: 0 } + slot_5: { enabled: true, x: 33.5, y: 71.0, z: -9.5, yaw: 180, pitch: 0 } + armor_stands: { } diff --git a/configuration/skyblock/garden/pest_drops.yml b/configuration/skyblock/garden/pest_drops.yml new file mode 100644 index 0000000000..e9343ef355 --- /dev/null +++ b/configuration/skyblock/garden/pest_drops.yml @@ -0,0 +1,137 @@ +universal_drops: + regular: + - { item: COINS, amount: 1000, chance: 1.0 } + - { item: FARMING_XP, amount: 1500, chance: 1.0 } + - { item: EXPERIENCE, amount: 10, chance: 1.0 } + - { item: DUNG_DYE, amount: 1, chance: 0.000002 } + - { item: COMPOST, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + - { item: HONEY_JAR, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + - { item: DUNG, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + - { item: PLANT_MATTER, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + - { item: TASTY_CHEESE, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + - { item: JELLY, amount: 1, chance: 0.10, exclusive_group: REGULAR_BONUS } + field_mouse: + - { item: COINS, amount: 10000, chance: 1.0 } + - { item: FARMING_XP, amount: 6000, chance: 1.0 } + - { item: EXPERIENCE, amount: 50, chance: 1.0 } + - { item: COMPOST, amount: 1, chance: 1.0 } + - { item: HONEY_JAR, amount: 1, chance: 1.0 } + - { item: DUNG, amount: 1, chance: 1.0 } + - { item: PLANT_MATTER, amount: 1, chance: 1.0 } + - { item: TASTY_CHEESE, amount: 1, chance: 1.0 } + - { item: JELLY, amount: 1, chance: 1.0 } + - { item: DUNG_DYE, amount: 1, chance: 0.000002 } +drops: + - pest: FLY + guaranteed: + - { item: ENCHANTED_WHEAT, amount: 1, scaling_per_fortune: 35 } + rare: + - { item: BEADY_EYES, chance: 0.015 } + - { item: PRETTY_FLY_VINYL, chance: 0.02 } + - { item: ENCHANTED_HAY_BALE, amount: 3, chance: 0.005 } + - pest: CRICKET + guaranteed: + - { item: ENCHANTED_CARROT, amount: 3, scaling_per_fortune: 10.5 } + rare: + - { item: CRICKET_CHOIR_VINYL, chance: 0.02 } + - { item: ENCHANTED_GOLDEN_CARROT, amount: 10, chance: 0.005 } + - { item: CHIRPING_STEREO, chance: 0.005 } + - pest: LOCUST + guaranteed: + - { item: ENCHANTED_POTATO, amount: 3, scaling_per_fortune: 10.5 } + rare: + - { item: CICADA_SYMPHONY_VINYL, chance: 0.02 } + - { item: ENCHANTED_BAKED_POTATO, amount: 10, chance: 0.005 } + - { item: SUNDER_VI_BOOK, chance: 0.005 } + - pest: RAT + guaranteed: + - { item: ENCHANTED_PUMPKIN, amount: 1, scaling_per_fortune: 35 } + rare: + - { item: RODENT_REVOLUTION_VINYL, chance: 0.02 } + - { item: POLISHED_PUMPKIN, amount: 3, chance: 0.005 } + - { item: RAT_PET, chance: 0.002 } + - pest: MOSQUITO + guaranteed: + - { item: ENCHANTED_SUGAR, amount: 2, scaling_per_fortune: 17.5 } + rare: + - { item: BUZZIN_BEATS_VINYL, chance: 0.02 } + - { item: ENCHANTED_SUGAR_CANE, amount: 6, chance: 0.005 } + - { item: CLIPPED_WINGS, chance: 0.01 } + - pest: EARTHWORM + guaranteed: + - { item: ENCHANTED_MELON_SLICE, amount: 5, scaling_per_fortune: 7 } + rare: + - { item: BOOKWORMS_FAVORITE_BOOK, chance: 0.02 } + - { item: EARTHWORM_ENSEMBLE_VINYL, chance: 0.02 } + - { item: ENCHANTED_MELON, amount: 15, chance: 0.005 } + - pest: MITE + guaranteed: + - { item: ENCHANTED_CACTUS_GREEN, amount: 2, scaling_per_fortune: 17.5 } + rare: + - { item: DYNAMITES_VINYL, chance: 0.02 } + - { item: ENCHANTED_CACTUS, amount: 6, chance: 0.005 } + - { item: ATMOSPHERIC_FILTER, chance: 0.0025 } + - pest: MOTH + guaranteed: + - { item: ENCHANTED_COCOA_BEAN, amount: 3, scaling_per_fortune: 12 } + rare: + - { item: WINGS_OF_HARMONY_VINYL, chance: 0.02 } + - { item: ENCHANTED_COOKIE, amount: 9, chance: 0.005 } + - { item: WRIGGLING_LARVA, chance: 0.005 } + - pest: SLUG + guaranteed: + - { item: ENCHANTED_RED_MUSHROOM, amount: 1, chance: 0.5, scaling_per_fortune: 35, exclusive_group: SLUG_MUSHROOM } + - { item: ENCHANTED_BROWN_MUSHROOM, amount: 1, chance: 0.5, scaling_per_fortune: 35, exclusive_group: SLUG_MUSHROOM } + rare: + - { item: SLOW_AND_GROOVY_VINYL, chance: 0.02 } + - { item: ENCHANTED_RED_MUSHROOM_BLOCK, amount: 3, chance: 0.0025 } + - { item: ENCHANTED_BROWN_MUSHROOM_BLOCK, amount: 3, chance: 0.0025 } + - { item: SLUG_PET_EPIC, chance: 0.005, exclusive_group: SLUG_PET } + - { item: SLUG_PET_LEGENDARY, chance: 0.001, exclusive_group: SLUG_PET } + - pest: BEETLE + guaranteed: + - { item: ENCHANTED_NETHER_WART, amount: 3, scaling_per_fortune: 12 } + rare: + - { item: PESTERMINATOR_I_BOOK, chance: 0.035 } + - { item: NOT_JUST_A_PEST_VINYL, chance: 0.02 } + - { item: MUTANT_NETHER_WART, amount: 9, chance: 0.005 } + - pest: DRAGONFLY + guaranteed: + - { item: ENCHANTED_SUNFLOWER, amount: 2, scaling_per_fortune: 17.5 } + rare: + - { item: IMAGINE_DRAGONFLIES_VINYL, chance: 0.02 } + - { item: VERMIN_VAPORIZER_GARDEN_CHIP, chance: 0.01 } + - { item: COMPACTED_SUNFLOWER, amount: 6, chance: 0.005 } + - pest: FIREFLY + guaranteed: + - { item: ENCHANTED_MOONFLOWER, amount: 2, scaling_per_fortune: 17.5 } + rare: + - { item: FIREFLY_IN_THE_HOLE_VINYL, chance: 0.02 } + - { item: FIRE_IN_A_BOTTLE, chance: 0.01 } + - { item: COMPACTED_MOONFLOWER, amount: 6, chance: 0.005 } + - pest: PRAYING_MANTIS + guaranteed: + - { item: ENCHANTED_WILD_ROSE, amount: 2, scaling_per_fortune: 17.5 } + rare: + - { item: PRAY_FOR_ME_VINYL, chance: 0.02 } + - { item: MANTID_CLAW, chance: 0.01 } + - { item: COMPACTED_WILD_ROSE, amount: 6, chance: 0.005 } + - pest: FIELD_MOUSE + guaranteed: + - { item: ENCHANTED_WHEAT, amount: 1, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_CARROT, amount: 3, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_POTATO, amount: 3, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_PUMPKIN, amount: 1, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_SUGAR, amount: 2, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_MELON_SLICE, amount: 5, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_CACTUS_GREEN, amount: 2, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_COCOA_BEAN, amount: 3, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_RED_MUSHROOM, amount: 1, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_BROWN_MUSHROOM, amount: 1, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_NETHER_WART, amount: 3, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_MOONFLOWER, amount: 2, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_SUNFLOWER, amount: 2, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + - { item: ENCHANTED_WILD_ROSE, amount: 2, chance: 1.0, exclusive_group: FIELD_MOUSE_CROP } + rare: + - { item: SQUEAKY_TOY, chance: 0.025 } + - { item: SQUEAKY_MOUSEMAT, chance: 0.005 } diff --git a/configuration/skyblock/garden/pests.yml b/configuration/skyblock/garden/pests.yml new file mode 100644 index 0000000000..82afb1bddb --- /dev/null +++ b/configuration/skyblock/garden/pests.yml @@ -0,0 +1,35 @@ +base_spawn_chance: 0.002 +sprayonator_bonus_percent: 50 +atmospheric_filter_autumn_bonus_percent: 15 +base_cooldown_seconds: 300 +offline_spawn_cap_per_hour: 1 +max_spawn_cooldown_seconds: 1200 +min_spawn_cooldown_seconds: 75 +start_garden_level: 5 +adjacent_plot_spawns_only: true +trap_spawn_minutes: 15 +trap_field_mouse_chances: + PEST_TRAP: 0.0278 + MOUSE_TRAP: 0.0789 + VERMIN_TRAP: 0.1026 +fortune_penalties: + - { pests: 4, percent: 5 } + - { pests: 5, percent: 15 } + - { pests: 6, percent: 30 } + - { pests: 7, percent: 50 } + - { pests: 8, percent: 75 } +registry: + - { id: FLY, display_name: Fly, crop: WHEAT, garden_level: 5, sprayonator_item: DUNG, vinyl: PRETTY_FLY_VINYL } + - { id: CRICKET, display_name: Cricket, crop: CARROT, garden_level: 5, sprayonator_item: HONEY_JAR, vinyl: CRICKET_CHOIR_VINYL } + - { id: LOCUST, display_name: Locust, crop: POTATO, garden_level: 5, sprayonator_item: PLANT_MATTER, vinyl: CICADA_SYMPHONY_VINYL } + - { id: RAT, display_name: Rat, crop: PUMPKIN, garden_level: 5, sprayonator_item: TASTY_CHEESE, vinyl: RODENT_REVOLUTION_VINYL } + - { id: MOSQUITO, display_name: Mosquito, crop: SUGAR_CANE, garden_level: 5, sprayonator_item: COMPOST, vinyl: BUZZIN_BEATS_VINYL } + - { id: EARTHWORM, display_name: Earthworm, crop: MELON_SLICE, garden_level: 6, sprayonator_item: null, vinyl: EARTHWORM_ENSEMBLE_VINYL } + - { id: MITE, display_name: Mite, crop: CACTUS, garden_level: 7, sprayonator_item: TASTY_CHEESE, vinyl: DYNAMITES_VINYL } + - { id: MOTH, display_name: Moth, crop: COCOA_BEANS, garden_level: 8, sprayonator_item: HONEY_JAR, vinyl: WINGS_OF_HARMONY_VINYL } + - { id: SLUG, display_name: Slug, crop: MUSHROOM, garden_level: 9, sprayonator_item: PLANT_MATTER, vinyl: SLOW_AND_GROOVY_VINYL } + - { id: BEETLE, display_name: Beetle, crop: NETHER_WART, garden_level: 10, sprayonator_item: DUNG, vinyl: NOT_JUST_A_PEST_VINYL } + - { id: FIREFLY, display_name: Firefly, crop: MOONFLOWER, garden_level: 11, sprayonator_item: JELLY, vinyl: FIREFLY_IN_THE_HOLE_VINYL, night_only: true } + - { id: DRAGONFLY, display_name: Dragonfly, crop: SUNFLOWER, garden_level: 11, sprayonator_item: null, vinyl: IMAGINE_DRAGONFLIES_VINYL, day_only: true } + - { id: PRAYING_MANTIS, display_name: Praying Mantis, crop: WILD_ROSE, garden_level: 12, sprayonator_item: null, vinyl: PRAY_FOR_ME_VINYL } + - { id: FIELD_MOUSE, display_name: Field Mouse, crop: RANDOM, garden_level: 5, trap_only: true } diff --git a/configuration/skyblock/garden/plots.yml b/configuration/skyblock/garden/plots.yml new file mode 100644 index 0000000000..ae4bc3237a --- /dev/null +++ b/configuration/skyblock/garden/plots.yml @@ -0,0 +1,253 @@ +world: + plot_size: 96 + min_x: -240 + max_x: 239 + min_z: -240 + max_z: 239 + central_plot_id: central + default_plot: + min_x: -48 + max_x: 47 + min_z: -48 + max_z: 47 + barn_swap_region: + min_x: -33 + max_x: 35 + min_z: -47 + max_z: -5 +plot_bonuses: + farming_fortune: 3 + skyblock_xp: 5 +cleaning: + leaf_decay_threshold_percent: 70 + counted_blocks: + - DIRT + - GRASS_BLOCK + - STONE + - SHORT_GRASS + - TALL_GRASS + - FERN + - LARGE_FERN + - OAK_LOG + - SPRUCE_LOG + - BIRCH_LOG + - JUNGLE_LOG + - ACACIA_LOG + - DARK_OAK_LOG + - MANGROVE_LOG + - CHERRY_LOG + - AZALEA_LEAVES + - FLOWERING_AZALEA_LEAVES + - OAK_LEAVES + - SPRUCE_LEAVES + - BIRCH_LEAVES + - JUNGLE_LEAVES + - ACACIA_LEAVES + - DARK_OAK_LEAVES + - MANGROVE_LEAVES +unlock_requirements: + group_level_requirements: + A: 1 + B: 3 + C: 5 + D: 7 + cost_curve: + - unlock_index: 1 + item: COMPOST + amount: 1 + - unlock_index: 2 + item: COMPOST + amount: 2 + - unlock_index: 3 + item: COMPOST + amount: 4 + - unlock_index: 4 + item: COMPOST + amount: 8 + - unlock_index: 5 + item: COMPOST + amount: 16 + - unlock_index: 6 + item: COMPOST + amount: 24 + - unlock_index: 7 + item: COMPOST + amount: 32 + - unlock_index: 8 + item: COMPOST + amount: 48 + - unlock_index: 9 + item: COMPOST + amount: 64 + - unlock_index: 10 + item: COMPOST + amount: 96 + - unlock_index: 11 + item: COMPOST + amount: 128 + - unlock_index: 12 + item: COMPOST_BUNDLE + amount: 1 + - unlock_index: 13 + item: COMPOST_BUNDLE + amount: 1 + - unlock_index: 14 + item: COMPOST_BUNDLE + amount: 2 + - unlock_index: 15 + item: COMPOST_BUNDLE + amount: 2 + - unlock_index: 16 + item: COMPOST_BUNDLE + amount: 3 + - unlock_index: 17 + item: COMPOST_BUNDLE + amount: 3 + - unlock_index: 18 + item: COMPOST_BUNDLE + amount: 4 + - unlock_index: 19 + item: COMPOST_BUNDLE + amount: 5 + - unlock_index: 20 + item: COMPOST_BUNDLE + amount: 7 + - unlock_index: 21 + item: COMPOST_BUNDLE + amount: 8 + - unlock_index: 22 + item: COMPOST_BUNDLE + amount: 10 + - unlock_index: 23 + item: COMPOST_BUNDLE + amount: 12 + - unlock_index: 24 + item: COMPOST_BUNDLE + amount: 15 +plots: + - id: central + display_name: Central Plot + group: A + garden_level: 0 + default_unlocked: true + bounds: { min_x: -48, max_x: 47, min_z: -48, max_z: 47 } + - id: north + display_name: North Plot + group: A + garden_level: 1 + bounds: { min_x: -48, max_x: 47, min_z: -144, max_z: -49 } + - id: south + display_name: South Plot + group: A + garden_level: 1 + bounds: { min_x: -48, max_x: 47, min_z: 48, max_z: 143 } + - id: west + display_name: West Plot + group: A + garden_level: 1 + bounds: { min_x: -144, max_x: -49, min_z: -48, max_z: 47 } + - id: east + display_name: East Plot + group: A + garden_level: 1 + bounds: { min_x: 48, max_x: 143, min_z: -48, max_z: 47 } + - id: north_west + display_name: North West Plot + group: B + garden_level: 3 + bounds: { min_x: -144, max_x: -49, min_z: -144, max_z: -49 } + - id: north_east + display_name: North East Plot + group: B + garden_level: 3 + bounds: { min_x: 48, max_x: 143, min_z: -144, max_z: -49 } + - id: south_west + display_name: South West Plot + group: B + garden_level: 3 + bounds: { min_x: -144, max_x: -49, min_z: 48, max_z: 143 } + - id: south_east + display_name: South East Plot + group: B + garden_level: 3 + bounds: { min_x: 48, max_x: 143, min_z: 48, max_z: 143 } + - id: north_far + display_name: Far North Plot + group: C + garden_level: 5 + bounds: { min_x: -48, max_x: 47, min_z: -240, max_z: -145 } + - id: south_far + display_name: Far South Plot + group: C + garden_level: 5 + bounds: { min_x: -48, max_x: 47, min_z: 144, max_z: 239 } + - id: west_far + display_name: Far West Plot + group: C + garden_level: 5 + bounds: { min_x: -240, max_x: -145, min_z: -48, max_z: 47 } + - id: east_far + display_name: Far East Plot + group: C + garden_level: 5 + bounds: { min_x: 144, max_x: 239, min_z: -48, max_z: 47 } + - id: north_outer_west + display_name: North Outer West Plot + group: C + garden_level: 5 + bounds: { min_x: -144, max_x: -49, min_z: -240, max_z: -145 } + - id: north_outer_east + display_name: North Outer East Plot + group: C + garden_level: 5 + bounds: { min_x: 48, max_x: 143, min_z: -240, max_z: -145 } + - id: south_outer_west + display_name: South Outer West Plot + group: C + garden_level: 5 + bounds: { min_x: -144, max_x: -49, min_z: 144, max_z: 239 } + - id: south_outer_east + display_name: South Outer East Plot + group: C + garden_level: 5 + bounds: { min_x: 48, max_x: 143, min_z: 144, max_z: 239 } + - id: west_outer_north + display_name: West Outer North Plot + group: C + garden_level: 5 + bounds: { min_x: -240, max_x: -145, min_z: -144, max_z: -49 } + - id: west_outer_south + display_name: West Outer South Plot + group: C + garden_level: 5 + bounds: { min_x: -240, max_x: -145, min_z: 48, max_z: 143 } + - id: east_outer_north + display_name: East Outer North Plot + group: C + garden_level: 5 + bounds: { min_x: 144, max_x: 239, min_z: -144, max_z: -49 } + - id: east_outer_south + display_name: East Outer South Plot + group: C + garden_level: 5 + bounds: { min_x: 144, max_x: 239, min_z: 48, max_z: 143 } + - id: north_west_far + display_name: Far North West Plot + group: D + garden_level: 7 + bounds: { min_x: -240, max_x: -145, min_z: -240, max_z: -145 } + - id: north_east_far + display_name: Far North East Plot + group: D + garden_level: 7 + bounds: { min_x: 144, max_x: 239, min_z: -240, max_z: -145 } + - id: south_west_far + display_name: Far South West Plot + group: D + garden_level: 7 + bounds: { min_x: -240, max_x: -145, min_z: 144, max_z: 239 } + - id: south_east_far + display_name: Far South East Plot + group: D + garden_level: 7 + bounds: { min_x: 144, max_x: 239, min_z: 144, max_z: 239 } diff --git a/configuration/skyblock/garden/visitor_dialogue.yml b/configuration/skyblock/garden/visitor_dialogue.yml new file mode 100644 index 0000000000..2636b05ff1 --- /dev/null +++ b/configuration/skyblock/garden/visitor_dialogue.yml @@ -0,0 +1,58 @@ +ambient_dialogue: + time_queries: + enabled: true + seasonal_small_talk: + enabled: true + generic_pairs: [ ] + +npcs: + sam: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Welcome to your Garden! + - Use the Desk to manage plots, skins, upgrades, and visitors. + anita: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Earn Jacob's Tickets and medals in farming contests. + - Come back to me when you're ready to spend them. + pamela: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Jacob's contests come around fast in the Garden. + - Keep farming and check in before the next one starts. + jacob: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Every 3 SkyBlock days, I host a contest for 3 crops. + - Collect at least 100 of a contest crop to earn rewards. + jeff: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Garden Chips get stronger as you redeem more copies and spend Sowdust. + - Use my menu to redeem chips and level them up. + phillip: + dialogue_sets: + - key: hello + trigger: first_interaction + first_interaction_only: true + lines: + - Pests are a nuisance, but they also drop useful Garden materials. + - Keep your plots clean and visit me when you need pest tools. + +visitors: { } diff --git a/configuration/skyblock/garden/visitors.yml b/configuration/skyblock/garden/visitors.yml new file mode 100644 index 0000000000..3e38fe4e4e --- /dev/null +++ b/configuration/skyblock/garden/visitors.yml @@ -0,0 +1,1595 @@ +expected_unique_visitors: 137 +arrival_minutes: + base: 15 + fifty_unique: 12 + one_hundred_unique: 9 +farming_speedup_multiplier: 3 +finnegan_blooming_business_reduction_percent: 25 +max_visible_visitors: 5 +max_queued_visitors: 1 +rarity_weights: + UNCOMMON: 50 + RARE: 15 + LEGENDARY: 3 + MYTHIC: 0.3 + SPECIAL: 0.03 +rarity_request_multipliers: + UNCOMMON: 1 + RARE: 1.5 + LEGENDARY: 3 + MYTHIC: 3 + SPECIAL: 3 +farming_xp_rarity_multipliers: + UNCOMMON: 0.05 + RARE: 0.06 + LEGENDARY: 0.075 + MYTHIC: 0.075 + SPECIAL: 0.075 +copper_rarity_multipliers: + UNCOMMON: 1 + RARE: 2 + LEGENDARY: 5 + MYTHIC: 5 + SPECIAL: 5 +bits_base: 5 +bonus_rewards: + - chance: 0.02 + min_rarity: UNCOMMON + rewards: + - { type: ITEM, key: FLOWERING_BOUQUET, amount: 1 } + - chance: 0.002 + min_rarity: UNCOMMON + rewards: + - { type: ITEM, key: FRUIT_BOWL, amount: 1 } + - chance: 0.001 + min_rarity: UNCOMMON + rewards: + - { type: ITEM, key: OVERGROWN_GRASS, amount: 1 } + - chance: 0.004 + min_rarity: RARE + rewards: + - { type: ITEM, key: GREEN_BANDANA, amount: 1 } + - chance: 0.0001 + min_rarity: UNCOMMON + rewards: + - { type: ITEM, key: COPPER_DYE, amount: 1 } +base_item_ranges: + - { level: 1, min: 500, max: 1000 } + - { level: 2, min: 1250, max: 2500 } + - { level: 3, min: 2000, max: 4000 } + - { level: 4, min: 5000, max: 10000 } + - { level: 5, min: 10000, max: 20000 } + - { level: 6, min: 20000, max: 40000 } + - { level: 7, min: 30000, max: 60000 } + - { level: 8, min: 40000, max: 80000 } + - { level: 9, min: 50000, max: 100000 } + - { level: 10, min: 70000, max: 140000 } + - { level: 11, min: 75000, max: 150000 } + - { level: 12, min: 80000, max: 160000 } + - { level: 13, min: 85000, max: 170000 } + - { level: 14, min: 90000, max: 180000 } + - { level: 15, min: 95000, max: 190000 } +garden_xp_by_level: + - { level: 1, uncommon: 2, rare: 4, legendary: 10, mythic: 10, special: 10 } + - { level: 2, uncommon: 3, rare: 5, legendary: 13, mythic: 13, special: 13 } + - { level: 3, uncommon: 3, rare: 6, legendary: 15, mythic: 15, special: 15 } + - { level: 4, uncommon: 4, rare: 8, legendary: 20, mythic: 20, special: 20 } + - { level: 5, uncommon: 5, rare: 10, legendary: 25, mythic: 25, special: 25 } + - { level: 6, uncommon: 6, rare: 12, legendary: 30, mythic: 30, special: 30 } + - { level: 7, uncommon: 8, rare: 16, legendary: 40, mythic: 40, special: 40 } + - { level: 8, uncommon: 10, rare: 20, legendary: 50, mythic: 50, special: 50 } + - { level: 9, uncommon: 12, rare: 24, legendary: 60, mythic: 60, special: 60 } + - { level: 10, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } + - { level: 11, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } + - { level: 12, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } + - { level: 13, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } + - { level: 14, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } + - { level: 15, uncommon: 15, rare: 30, legendary: 75, mythic: 75, special: 75 } +item_quantity_multipliers: + WHEAT: 1 + BREAD: 1 + CARROT: 3.5 + GOLDEN_CARROT: 3.5 + POTATO: 3 + PUMPKIN: 0.85 + SUGAR_CANE: 2 + MELON_SLICE: 4 + CACTUS: 1.5 + COCOA_BEANS: 1 + MUSHROOM: 0.95 + NETHER_WART: 3 + SUNFLOWER: 1 + MOONFLOWER: 1 + WILD_ROSE: 1 + RAW_MUTTON: 1 + RAW_PORKCHOP: 1 + RAW_RABBIT: 1 + COMPOST: 1 +request_item_aliases: + RAW_MUTTON: MUTTON + ENCHANTED_RAW_MUTTON: ENCHANTED_MUTTON + ENCHANTED_RAW_PORKCHOP: ENCHANTED_PORK + ENCHANTED_COOKED_PORKCHOP: ENCHANTED_GRILLED_PORK + ENCHANTED_MELON_SLICE: ENCHANTED_MELON + ENCHANTED_MELON: ENCHANTED_MELON_BLOCK +request_compaction: + WHEAT: + profiles: + - id: WHEAT + weight: 1 + stages: + - { item: WHEAT, raw_cost: 1 } + - { item: ENCHANTED_WHEAT, raw_cost: 160 } + - { item: ENCHANTED_HAY_BALE, raw_cost: 25600 } + BREAD: + profiles: + - id: BREAD + weight: 1 + stages: + - { item: BREAD, raw_cost: 3 } + - { item: ENCHANTED_BREAD, raw_cost: 480 } + CARROT: + profiles: + - id: CARROT + weight: 1 + stages: + - { item: CARROT, raw_cost: 1 } + - { item: ENCHANTED_CARROT, raw_cost: 160 } + GOLDEN_CARROT: + profiles: + - id: GOLDEN_CARROT + weight: 1 + stages: + - { item: GOLDEN_CARROT, raw_cost: 1 } + - { item: ENCHANTED_GOLDEN_CARROT, raw_cost: 160 } + POTATO: + profiles: + - id: POTATO + weight: 1 + stages: + - { item: POTATO, raw_cost: 1 } + - { item: ENCHANTED_POTATO, raw_cost: 160 } + - { item: ENCHANTED_BAKED_POTATO, raw_cost: 25600 } + PUMPKIN: + profiles: + - id: PUMPKIN + weight: 1 + stages: + - { item: PUMPKIN, raw_cost: 1 } + - { item: ENCHANTED_PUMPKIN, raw_cost: 160 } + - { item: POLISHED_PUMPKIN, raw_cost: 25600 } + SUGAR_CANE: + profiles: + - id: SUGAR_CANE + weight: 1 + stages: + - { item: SUGAR_CANE, raw_cost: 1 } + - { item: ENCHANTED_SUGAR, raw_cost: 160 } + - { item: ENCHANTED_SUGAR_CANE, raw_cost: 25600 } + MELON_SLICE: + profiles: + - id: MELON_SLICE + weight: 1 + stages: + - { item: MELON_SLICE, raw_cost: 1 } + - { item: ENCHANTED_MELON, raw_cost: 160 } + - { item: ENCHANTED_MELON_BLOCK, raw_cost: 25600 } + CACTUS: + profiles: + - id: CACTUS + weight: 1 + stages: + - { item: CACTUS, raw_cost: 1 } + - { item: ENCHANTED_CACTUS_GREEN, raw_cost: 160 } + - { item: ENCHANTED_CACTUS, raw_cost: 25600 } + COCOA_BEANS: + profiles: + - id: COCOA_BEANS + weight: 1 + stages: + - { item: COCOA_BEANS, raw_cost: 1 } + - { item: ENCHANTED_COCOA_BEANS, raw_cost: 160 } + - { item: ENCHANTED_COOKIE, raw_cost: 20480 } + MUSHROOM: + profiles: + - id: RED_MUSHROOM + weight: 1 + stages: + - { item: RED_MUSHROOM, raw_cost: 1 } + - { item: ENCHANTED_RED_MUSHROOM, raw_cost: 160 } + - { item: ENCHANTED_RED_MUSHROOM_BLOCK, raw_cost: 1440 } + - id: BROWN_MUSHROOM + weight: 1 + stages: + - { item: BROWN_MUSHROOM, raw_cost: 1 } + - { item: ENCHANTED_BROWN_MUSHROOM, raw_cost: 160 } + - { item: ENCHANTED_BROWN_MUSHROOM_BLOCK, raw_cost: 1440 } + NETHER_WART: + profiles: + - id: NETHER_WART + weight: 1 + stages: + - { item: NETHER_WART, raw_cost: 1 } + - { item: ENCHANTED_NETHER_WART, raw_cost: 160 } + - { item: MUTANT_NETHER_WART, raw_cost: 25600 } + SUNFLOWER: + profiles: + - id: SUNFLOWER + weight: 1 + stages: + - { item: SUNFLOWER, raw_cost: 1 } + - { item: ENCHANTED_SUNFLOWER, raw_cost: 160 } + - { item: COMPACTED_SUNFLOWER, raw_cost: 25600 } + MOONFLOWER: + profiles: + - id: MOONFLOWER + weight: 1 + stages: + - { item: MOONFLOWER, raw_cost: 1 } + - { item: ENCHANTED_MOONFLOWER, raw_cost: 160 } + - { item: COMPACTED_MOONFLOWER, raw_cost: 25600 } + WILD_ROSE: + profiles: + - id: WILD_ROSE + weight: 1 + stages: + - { item: WILD_ROSE, raw_cost: 1 } + - { item: ENCHANTED_WILD_ROSE, raw_cost: 160 } + - { item: COMPACTED_WILD_ROSE, raw_cost: 25600 } + RAW_MUTTON: + profiles: + - id: MUTTON + weight: 1 + stages: + - { item: MUTTON, raw_cost: 1 } + - { item: ENCHANTED_MUTTON, raw_cost: 160 } + - { item: ENCHANTED_COOKED_MUTTON, raw_cost: 25600 } + RAW_PORKCHOP: + profiles: + - id: RAW_PORKCHOP + weight: 1 + stages: + - { item: RAW_PORKCHOP, raw_cost: 1 } + - { item: ENCHANTED_PORK, raw_cost: 160 } + - { item: ENCHANTED_GRILLED_PORK, raw_cost: 25600 } + RAW_RABBIT: + profiles: + - id: RAW_RABBIT + weight: 1 + stages: + - { item: RAW_RABBIT, raw_cost: 1 } + - { item: ENCHANTED_RAW_RABBIT, raw_cost: 160 } + COMPOST: + profiles: + - id: COMPOST + weight: 1 + stages: + - { item: COMPOST, raw_cost: 1 } +registry: + - id: adventurer + display_name: Adventurer + rarity: UNCOMMON + garden_level: 3 + requirements: + - type: SPOKEN_TO_NPC + key: ADVENTURER + display: "Talk to the Adventurer" + wanted_items: + - POTATO + - id: alchemage + display_name: Alchemage + rarity: RARE + wanted_items: + - CARROT + - id: alchemist + display_name: Alchemist + rarity: UNCOMMON + garden_level: 10 + requirements: + - type: SPOKEN_TO_NPC + key: ALCHEMIST + display: "Talk to the Alchemist" + wanted_items: + - NETHER_WART + - id: an + display_name: An + rarity: UNCOMMON + wanted_items: + - NETHER_WART + - id: andrew + display_name: Andrew + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ANDREW + display: "Talk to Andrew" + wanted_items: + - ANY + - id: anita + display_name: Anita + rarity: UNCOMMON + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: ANITA + display: "Talk to Anita" + wanted_items: + - CARROT + - id: archaeologist + display_name: Archaeologist + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: arthur + display_name: Arthur + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ARTHUR + display: "Talk to Arthur" + wanted_items: + - WHEAT + - id: baker + display_name: Baker + rarity: LEGENDARY + requirements: + - type: MAYOR_PERK_ACTIVE + key: BLOOMING_BUSINESS + display: "Mayor Finnegan's Blooming Business perk" + - type: ITEM_EXPORTED + key: FINE_FLOUR + amount: 3000 + display: "Export 3,000x Fine Flour" + wanted_items: + - FINE_FLOUR + - id: banker_broadjaw + display_name: Banker Broadjaw + rarity: UNCOMMON + garden_level: 7 + requirements: + - type: SPOKEN_TO_NPC + key: BANKER_BROADJAW + display: "Talk to Banker Broadjaw" + wanted_items: + - CACTUS + - id: bartender + display_name: Bartender + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: BARTENDER + display: "Talk to the Bartender" + wanted_items: + - ANY 2 + - id: bednom + display_name: Bednom + rarity: UNCOMMON + wanted_items: + - COCOA_BEANS + - id: beth + display_name: Beth + rarity: LEGENDARY + requirements: + - type: SPOKEN_TO_NPC + key: BETH + display: "Talk to Beth" + wanted_items: + - ANY + - id: bruuh + display_name: Bruuh + rarity: LEGENDARY + wanted_items: + - ANY + guaranteed_rewards: + - { type: FAIRY_SOUL, amount: 1, display: "Fairy Soul" } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: carpenter + display_name: Carpenter + rarity: RARE + wanted_items: + - MELON_SLICE + - COCOA_BEANS + guaranteed_rewards: + - { type: ITEM, key: GREENHOUSE_BLUEPRINT, amount: 1, first_visit_only: true } + - id: chantelle + display_name: Chantelle + rarity: RARE + wanted_items: + - SUNFLOWER + - MOONFLOWER + guaranteed_rewards: + - { type: ITEM, key: CARNIVAL_TICKET, amount: 5 } + - id: chief_scorn + display_name: Chief Scorn + rarity: LEGENDARY + wanted_items: + - NETHER_WART + - id: chunk + display_name: Chunk + rarity: UNCOMMON + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: SUPERBOOM_TNT, amount: 1 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: clerk_seraphine + display_name: Clerk Seraphine + rarity: LEGENDARY + garden_level: 5 + requirements: + - type: SPOKEN_TO_NPC + key: CLERK_SERAPHINE + display: "Talk to Clerk Seraphine" + wanted_items: + - SUGAR_CANE + - id: cold_enjoyer + display_name: Cold Enjoyer + rarity: RARE + wanted_items: + - CACTUS + - id: dalbrek + display_name: Dalbrek + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: DALBREK + display: "Talk to Dalbrek" + wanted_items: + - ANY + - id: dante_goon + display_name: Dante Goon + rarity: LEGENDARY + wanted_items: + - WILD_ROSE + guaranteed_rewards: + - { type: ITEM, key: POPPY, amount: 1 } + - id: duke + display_name: Duke + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: DUKE + display: "Talk to Duke" + wanted_items: + - ANY + - id: dulin + display_name: Dulin + rarity: LEGENDARY + wanted_items: + - POTATO + - id: duncan + display_name: Duncan + rarity: RARE + wanted_items: + - MELON_SLICE + - id: dusk + display_name: Dusk + rarity: UNCOMMON + garden_level: 6 + requirements: + - type: SPOKEN_TO_NPC + key: DUSK + display: "Talk to Dusk" + wanted_items: + - MELON_SLICE + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: LEVEL_ONE_RUNE, display: "Random Level I Rune" } + - id: elle + display_name: Elle + rarity: LEGENDARY + wanted_items: + - MUSHROOM + - id: emissary_carlton + display_name: Emissary Carlton + rarity: UNCOMMON + garden_level: 8 + requirements: + - type: SPOKEN_TO_NPC + key: EMISSARY_CARLTON + display: "Talk to Emissary Carlton" + wanted_items: + - COCOA_BEANS + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: EMISSARY_POWDER, display: "Mithril Powder or Gemstone Powder" } + - id: emissary_ceanna + display_name: Emissary Ceanna + rarity: UNCOMMON + garden_level: 3 + requirements: + - type: SPOKEN_TO_NPC + key: EMISSARY_CEANNA + display: "Talk to Emissary Ceanna" + wanted_items: + - POTATO + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: EMISSARY_POWDER, display: "Mithril Powder or Gemstone Powder" } + - id: emissary_fraiser + display_name: Emissary Fraiser + rarity: UNCOMMON + garden_level: 6 + requirements: + - type: SPOKEN_TO_NPC + key: EMISSARY_FRAISER + display: "Talk to Emissary Fraiser" + wanted_items: + - MELON_SLICE + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: EMISSARY_POWDER, display: "Mithril Powder or Gemstone Powder" } + - id: emissary_sisko + display_name: Emissary Sisko + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: EMISSARY_SISKO + display: "Talk to Emissary Sisko" + wanted_items: + - MUSHROOM + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: EMISSARY_POWDER, display: "Mithril Powder or Gemstone Powder" } + - id: emissary_wilson + display_name: Emissary Wilson + rarity: UNCOMMON + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: EMISSARY_WILSON + display: "Talk to Emissary Wilson" + wanted_items: + - CARROT + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: EMISSARY_POWDER, display: "Mithril Powder or Gemstone Powder" } + - id: erihann + display_name: Erihann + rarity: RARE + wanted_items: + - NETHER_WART + - id: fann + display_name: Fann + rarity: RARE + wanted_items: + - NETHER_WART + - MUSHROOM + - id: farm_merchant + display_name: Farm Merchant + rarity: UNCOMMON + wanted_items: + - WHEAT + - id: farmer_jon + display_name: Farmer Jon + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: FARMER_JON + display: "Talk to Farmer Jon" + wanted_items: + - SUGAR_CANE + - id: farmhand + display_name: Farmhand + rarity: UNCOMMON + garden_level: 3 + entity: + kind: VILLAGER + profession: FARMER + icon_item: WHEAT + requirements: + - type: SPOKEN_TO_NPC + key: FARMHAND + display: "Talk to Farmhand" + wanted_items: + - WHEAT + - CARROT + - POTATO + - id: fear_mongerer + display_name: Fear Mongerer + rarity: UNCOMMON + garden_level: 4 + requirements: + - type: SPOKEN_TO_NPC + key: FEAR_MONGERER + display: "Talk to the Fear Mongerer" + wanted_items: + - PUMPKIN + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: SPOOKY_CANDY, display: "Green Candy or Purple Candy" } + - id: felix + display_name: Felix + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: FELIX + display: "Talk to Felix" + wanted_items: + - ANY + - id: fisherman_gerald + display_name: Fisherman Gerald + rarity: UNCOMMON + requirements: + - type: SKILL_LEVEL_AT_LEAST + key: FISHING + amount: 5 + display: "Fishing V" + - type: SPOKEN_TO_NPC + key: FISHERMAN_GERALD + display: "Talk to Fisherman Gerald" + wanted_items: + - CARROT + - id: fragilis + display_name: Fragilis + rarity: RARE + garden_level: 7 + requirements: + - type: SPOKEN_TO_NPC + key: FRAGILIS + display: "Talk to Fragilis" + wanted_items: + - CACTUS + - id: friendly_hiker + display_name: Friendly Hiker + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: FRIENDLY_HIKER + display: "Talk to this Hiker" + wanted_items: + - RAW_MUTTON + - RAW_PORKCHOP + - RAW_RABBIT + - id: frozen_alex + display_name: Frozen Alex + rarity: RARE + wanted_items: + - ANY + guaranteed_rewards: + - { type: ESSENCE, key: ICE, amount: 10 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: gary + display_name: Gary + rarity: RARE + wanted_items: + - COCOA_BEANS + - id: gemma + display_name: Gemma + rarity: RARE + wanted_items: + - ANY + guaranteed_rewards: + - { type: ESSENCE, key: DIAMOND, amount: 10 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: geonathan_greatforge + display_name: Geonathan Greatforge + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: GEONATHAN_GREATFORGE + display: "Talk to Geonathan Greatforge" + wanted_items: + - COMPOST + override_rewards: + farming_xp: 15000 + copper: 30 + rewards: + - { type: ITEM, key: BIOFUEL, amount: 1 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + raw_unique_rewards: "Biofuel ++15,000 Farming XP +30 Copper" + - id: gimley + display_name: Gimley + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: GIMLEY + display: "Talk to Gimley" + wanted_items: + - WHEAT + - id: gold_forger + display_name: Gold Forger + rarity: RARE + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: GOLD_FORGER + display: "Talk to the Gold Forger" + wanted_items: + - id: GOLDEN_CARROT + fixed_amount: 1024 + guaranteed_rewards: + - { type: ESSENCE, key: GOLD, min: 2, max: 5 } + - id: grandma_wolf + display_name: Grandma Wolf + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: GRANDMA_WOLF + display: "Talk to Grandma Wolf" + wanted_items: + - ANY 3 + - id: guy + display_name: Guy + rarity: UNCOMMON + garden_level: 9 + requirements: + - type: SPOKEN_TO_NPC + key: GUY + display: "Talk to Guy" + wanted_items: + - MUSHROOM + - id: gwendolyn + display_name: Gwendolyn + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: GWENDOLYN + display: "Talk to Gwendolyn" + wanted_items: + - COCOA_BEANS + guaranteed_rewards: + - { type: ACCESS_FLAG, key: CRYSTAL_HOLLOWS_PASS, display: "Crystal Hollows Pass" } + - id: hendrik + display_name: Hendrik + rarity: RARE + wanted_items: + - POTATO + - CACTUS + - id: hoppity + display_name: Hoppity + rarity: LEGENDARY + wanted_items: + - COCOA_BEANS + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: CHOCOLATE_RABBIT, display: "1 Random Chocolate Rabbit" } + - id: hornum + display_name: Hornum + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: HORNUM + display: "Talk to Hornum" + wanted_items: + - WHEAT + - id: hungry_hiker + display_name: Hungry Hiker + rarity: UNCOMMON + requirements: + - type: PROFILE_FLAG + key: SAVE_THIS_HIKER + display: "Save this Hiker" + wanted_items: + - PUMPKIN + guaranteed_rewards: + - { type: ITEM, key: FARMING_EXP_BOOST_UNCOMMON, amount: 1, display: "Uncommon Farming Exp Boost" } + - id: iron_forger + display_name: Iron Forger + rarity: RARE + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: IRON_FORGER + display: "Talk to the Iron Forger" + wanted_items: + - CARROT + - id: jack + display_name: Jack + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: JACK + display: "Talk to Jack" + wanted_items: + - ANY + - id: jacob + display_name: Jacob + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: JACOB + display: "Talk to Jacob" + wanted_items: + - WHEAT + guaranteed_rewards: + - { type: JACOBS_TICKET, amount: 1, display: "Jacob's Ticket" } + - id: jacobus + display_name: Jacobus + rarity: UNCOMMON + wanted_items: + - SUGAR_CANE + - id: jamie + display_name: Jamie + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: JAMIE + display: "Talk to Jamie" + wanted_items: + - ANY + - id: jerry + display_name: Jerry + rarity: LEGENDARY + entity: + kind: VILLAGER + profession: NITWIT + icon_item: SLIME_BALL + wanted_items: + - ANY + first_visit_override: + garden_level: 1 + farming_xp: 500 + copper: 5 + wanted_items: + - BREAD + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + raw_unique_rewards: "+500 Farming XP (When Garden I) +5 Copper (When Garden I)" + - id: jotraeline_greatforge + display_name: Jotraeline Greatforge + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: JOTRAELINE_GREATFORGE + display: "Talk to Jotraeline Greatforge" + wanted_items: + - COMPOST + override_rewards: + farming_xp: 15000 + copper: 30 + rewards: + - { type: ITEM, key: BIOFUEL, amount: 1 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + raw_unique_rewards: "Biofuel ++15,000 Farming XP +30 Copper" + - id: lazy_miner + display_name: Lazy Miner + rarity: RARE + garden_level: 8 + requirements: + - type: SPOKEN_TO_NPC + key: LAZY_MINER + display: "Talk to the Lazy Miner" + wanted_items: + - COCOA_BEANS + - id: leo + display_name: Leo + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: LEO + display: "Talk to Leo" + wanted_items: + - ANY + - id: liam + display_name: Liam + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: LIAM + display: "Talk to Liam" + wanted_items: + - ANY + - id: librarian + display_name: Librarian + rarity: UNCOMMON + garden_level: 5 + entity: + kind: VILLAGER + profession: LIBRARIAN + icon_item: BOOK + requirements: + - type: SPOKEN_TO_NPC + key: LIBRARIAN + display: "Talk to the Librarian" + wanted_items: + - SUGAR_CANE + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: FARMING_BOOK, display: "Random Farming Enchanted Book" } + - id: lift_operator + display_name: Lift Operator + rarity: UNCOMMON + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: ludleth + display_name: Ludleth + rarity: MYTHIC + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: lumber_jack + display_name: Lumber Jack + rarity: UNCOMMON + garden_level: 9 + requirements: + - type: SPOKEN_TO_NPC + key: LUMBER_JACK + display: "Talk to the Lumberjack" + wanted_items: + - CARROT + - POTATO + - MELON_SLICE + - PUMPKIN + - MUSHROOM + - id: lumina + display_name: Lumina + rarity: RARE + garden_level: 4 + requirements: + - type: SPOKEN_TO_NPC + key: LUMINA + display: "Talk to Lumina" + wanted_items: + - PUMPKIN + - id: lynn + display_name: Lynn + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: LYNN + display: "Talk to Lynn" + wanted_items: + - ANY + - id: madame_eleanor_q_goldsworth_iii + display_name: Madame Eleanor Q. Goldsworth III + rarity: LEGENDARY + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: MADAME_ELEANOR_Q_GOLDSWORTH_III + display: "Talk to Madame Eleanor Q. Goldsworth III" + wanted_items: + - ENCHANTED_GOLDEN_CARROT + - id: maeve + display_name: Maeve + rarity: MYTHIC + garden_level: 12 + wanted_items: + - CROPIE + - SQUASH + - FERMENTO + guaranteed_rewards: + - { type: ITEM, key: HARVEST_HARBINGER_POTION, amount: 1, display: "Harvest Harbinger V Potion" } + - id: marco + display_name: Marco + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: marigold + display_name: Marigold + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mason + display_name: Mason + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: MASON + display: "Talk to Mason" + wanted_items: + - RAW_MUTTON + - POTATO + - CARROT + - MUSHROOM + - id: master_tactician_funk + display_name: Master Tactician Funk + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_aatrox + display_name: Mayor Aatrox + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_cole + display_name: Mayor Cole + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_diana + display_name: Mayor Diana + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_diaz + display_name: Mayor Diaz + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_finnegan + display_name: Mayor Finnegan + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_foxy + display_name: Mayor Foxy + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_marina + display_name: Mayor Marina + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: mayor_paul + display_name: Mayor Paul + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: moby + display_name: Moby + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: odawa + display_name: Odawa + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ODAWA + display: "Talk to Odawa" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: JUNGLE_KEY, amount: 1 } + - id: old_man_garry + display_name: Old Man Garry + rarity: RARE + garden_level: 8 + requirements: + - type: SPOKEN_TO_NPC + key: OLD_MAN_GARRY + display: "Talk to Old Man Garry" + wanted_items: + - COCOA_BEANS + - id: old_shaman_nyko + display_name: Old Shaman Nyko + rarity: UNCOMMON + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: ophelia + display_name: Ophelia + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: oringo + display_name: Oringo + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ORINGO + display: "Talk to Oringo" + wanted_items: + - WHEAT + guaranteed_rewards: + - { type: ITEM, key: PET_CAKE, amount: 1 } + - id: pearl_dealer + display_name: Pearl Dealer + rarity: UNCOMMON + wanted_items: + - NETHER_WART + - id: pest_wrangler + display_name: Pest Wrangler + rarity: UNCOMMON + requirements: + - type: MAYOR_PERK_ACTIVE + key: BLOOMING_BUSINESS + display: "Mayor Finnegan's Blooming Business perk" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: FINE_FLOUR, amount: 5 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + request_pool: "SPRAYONATOR_MATERIAL" + - id: pest_wrangler_question + display_name: Pest Wrangler? + rarity: LEGENDARY + requirements: + - type: MAYOR_PERK_ACTIVE + key: BLOOMING_BUSINESS + display: "Mayor Finnegan's Blooming Business perk" + wanted_items: + - TASTY_CHEESE + guaranteed_rewards: + - { type: ITEM, key: FINE_FLOUR, amount: 50 } + - id: pete + display_name: Pete + rarity: RARE + garden_level: 7 + requirements: + - type: PROFILE_FLAG + key: HEARD_BEAR_STORY + display: "Listen to the Bear Story" + wanted_items: + - SUGAR_CANE + - id: plumber_joe + display_name: Plumber Joe + rarity: UNCOMMON + garden_level: 7 + entity: + kind: VILLAGER + profession: TOOLSMITH + icon_item: WATER_BUCKET + requirements: + - type: SPOKEN_TO_NPC + key: PLUMBER_JOE + display: "Talk to Plumber Joe" + wanted_items: + - CACTUS + - id: puzzler + display_name: Puzzler + rarity: RARE + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: PUZZLER + display: "Talk to the Puzzler" + wanted_items: + - CARROT + - id: queen_mismyla + display_name: Queen Mismyla + rarity: RARE + garden_level: 2 + requirements: + - type: SPOKEN_TO_NPC + key: QUEEN_MISMYLA + display: "Talk to Queen Mismyla" + wanted_items: + - ANY 2 + - id: queen_nyx + display_name: Queen Nyx + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: ravenous_rhino + display_name: Ravenous Rhino + rarity: MYTHIC + requirements: + - type: MAYOR_PERK_ACTIVE + key: BLOOMING_BUSINESS + display: "Mayor Finnegan's Blooming Business perk" + wanted_items: + - BREAD + - ENCHANTED_CAKE + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: DEDICATION_BOOK, display: "Enchanted Book (Dedication IV)" } + - id: rhys + display_name: Rhys + rarity: UNCOMMON + garden_level: 4 + requirements: + - type: SPOKEN_TO_NPC + key: RHYS + display: "Talk to Rhys" + wanted_items: + - id: JACK_O_LANTERN + fixed_amount: 512 + override_rewards: + farming_xp: 1000 + copper: 2 + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + raw_unique_rewards: "+1,000 Farming XP +2 Copper" + - id: royal_resident + display_name: Royal Resident + rarity: RARE + garden_level: 7 + requirements: + - type: PROFILE_FLAG + key: ROYAL_RESIDENT_CAKE + display: "Recieve the Royal Resident's Cake" + wanted_items: + - WHEAT + - SUGAR_CANE + - id: resident_neighbor + display_name: Resident Neighbor + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ROYAL_RESIDENT_NEIGHBOR + display: "Talk to the Royal Resident (Neighbor)" + wanted_items: + - ANY + - id: resident_snooty + display_name: Resident Snooty + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: ROYAL_RESIDENT_SNOOTY + display: "Talk to the Royal Resident (Snooty)" + wanted_items: + - ANY + - id: rusty + display_name: Rusty + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: RUSTY + display: "Talk to Rusty" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: IRON_HOE, amount: 1 } + - id: ryu + display_name: Ryu + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: RYU + display: "Talk to Ryu" + wanted_items: + - ANY + - id: romero + display_name: Romero + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: ryan + display_name: Ryan + rarity: UNCOMMON + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: sargwyn + display_name: Sargwyn + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: SARGWYN + display: "Talk to Sargwyn" + wanted_items: + - WHEAT + - id: scout_scardius + display_name: Scout Scardius + rarity: UNCOMMON + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: seymour + display_name: Seymour + rarity: RARE + garden_level: 4 + requirements: + - type: SPOKEN_TO_NPC + key: SEYMOUR + display: "Talk to Seymour" + wanted_items: + - PUMPKIN + guaranteed_rewards: + - { type: RANDOM_POOL, pool_id: SEYMOUR_SPECIAL_ARMOR, display: "Random piece of Seymour's Special Armor" } + - id: shaggy + display_name: Shaggy + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: SHAGGY + display: "Talk to Shaggy" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: ARACHNE_FRAGMENT, amount: 1 } + - id: sherry + display_name: Sherry + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: shifty + display_name: Shifty + rarity: RARE + garden_level: 6 + requirements: + - type: SPOKEN_TO_NPC + key: SHIFTY + display: "Talk to the Shady Bartender" + wanted_items: + - MELON_SLICE + - id: sirius + display_name: Sirius + rarity: LEGENDARY + requirements: + - type: SPOKEN_TO_NPC + key: SIRIUS + display: "Talk to Sirius" + wanted_items: + - ANY + - id: spaceman + display_name: Spaceman + rarity: SPECIAL + garden_level: 15 + requirements: + - type: GARDEN_COUNTER_AT_LEAST + key: SERVED_UNIQUE_VISITORS + amount: 75 + display: "Serve 75 unique visitors" + wanted_items: + - ANY + override_rewards: + farming_xp: 100000 + copper: 100 + rewards: + - { type: ITEM, key: SPACE_HELMET, amount: 1 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + raw_unique_rewards: "Space Helmet ++100,000 Farming XP +100 Copper" + - id: spider_tamer + display_name: Spider Tamer + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: st_jerry + display_name: St. Jerry + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: stella + display_name: Stella + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: STELLA + display: "Talk to Stella" + wanted_items: + - ANY + - id: tammy + display_name: Tammy + rarity: RARE + garden_level: 7 + requirements: + - type: SPOKEN_TO_NPC + key: TAMMY + display: "Talk to Tammy" + wanted_items: + - CACTUS + - id: tarwen + display_name: Tarwen + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: TARWEN + display: "Talk to Tarwen" + wanted_items: + - WHEAT + - id: terry + display_name: Terry + rarity: UNCOMMON + garden_level: 6 + requirements: + - type: SPOKEN_TO_NPC + key: TERRY + display: "Talk to Terry" + wanted_items: + - MELON_SLICE + - id: tia_the_fairy + display_name: Tia the Fairy + rarity: RARE + requirements: + - type: SPOKEN_TO_NPC + key: TIA_THE_FAIRY + display: "Talk to Tia the Fairy" + wanted_items: + - ANY + guaranteed_rewards: + - { type: FAIRY_SOUL, amount: 1, display: "Fairy Soul" } + - id: tom + display_name: Tom + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: TOM + display: "Talk to Tom" + wanted_items: + - ANY + - id: tomioka + display_name: Tomioka + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: trevor + display_name: Trevor + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: TREVOR_THE_TRAPPER + display: "Talk to Trevor" + wanted_items: + - ANY + guaranteed_rewards: + - { type: PELTS, amount: 2, display: "2 Pelts" } + - id: trinity + display_name: Trinity + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: tyashoi_alchemist + display_name: Tyashoi Alchemist + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: tyzzo + display_name: Tyzzo + rarity: RARE + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: vargul + display_name: Vargul + rarity: LEGENDARY + wanted_items: + - ANY + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + completeness: "PARTIAL" + - id: vex + display_name: Vex + rarity: UNCOMMON + requirements: + - type: SPOKEN_TO_NPC + key: VEX + display: "Talk to Vex" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: DEAD_BUSH, amount: 1 } + - id: vincent + display_name: Vincent + rarity: LEGENDARY + requirements: + - type: ITEM_DONATED + key: DYE + display: "Donate a Dye" + wanted_items: + - SUNFLOWER + guaranteed_rewards: + - { type: ITEM, key: WILD_STRAWBERRY_DYE, amount: 1 } + - id: vinyl_collector + display_name: Vinyl Collector + rarity: RARE + requirements: + - type: MAYOR_PERK_ACTIVE + key: BLOOMING_BUSINESS + display: "Mayor Finnegan's Blooming Business perk" + wanted_items: + - ANY + guaranteed_rewards: + - { type: ITEM, key: FINE_FLOUR, amount: 15 } + source_metadata: + source: "FANDOM_GARDEN_VISITORS" + request_pool: "PEST_VINYL" + - id: weaponsmith + display_name: Weaponsmith + rarity: UNCOMMON + garden_level: 3 + requirements: + - type: SPOKEN_TO_NPC + key: WEAPONSMITH + display: "Talk to the Weaponsmith" + wanted_items: + - POTATO + - id: wizard + display_name: Wizard + rarity: UNCOMMON + garden_level: 3 + requirements: + - type: SPOKEN_TO_NPC + key: WIZARD + display: "Talk to the Wizard" + wanted_items: + - CARROT + first_visit_override: + wanted_items: + - POTATO + - id: xalx + display_name: Xalx + rarity: UNCOMMON + garden_level: 5 + requirements: + - type: SPOKEN_TO_NPC + key: XALX + display: "Talk to Xalx" + wanted_items: + - SUGAR + guaranteed_rewards: + - { type: ITEM, key: MYSTERIOUS_CROP, amount: 1 } + - id: zog + display_name: Zog + rarity: RARE + garden_level: 7 + requirements: + - type: SPOKEN_TO_NPC + key: ZOG + display: "Talk to Zog" + wanted_items: + - MELON_SLICE + - CACTUS diff --git a/configuration/skyblock/items/farming/garden024.yml b/configuration/skyblock/items/farming/garden024.yml new file mode 100644 index 0000000000..c8518ca407 --- /dev/null +++ b/configuration/skyblock/items/farming/garden024.yml @@ -0,0 +1,320 @@ +items: + - id: SOWDUST + material: green_dye + rarity: COMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Sowdust + - id: NOT_FINISHED_YET + + - id: ETHEREAL_VINE + material: vine + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Ethereal Vine + - id: NOT_FINISHED_YET + + - id: GREENHOUSE_BLUEPRINT + material: paper + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Greenhouse Blueprint + - id: NOT_FINISHED_YET + + - id: SUNFLOWER_OIL + material: honey_bottle + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Sunflower Oil + - id: NOT_FINISHED_YET + + - id: COALROOT + material: coal + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Coalroot + - id: NOT_FINISHED_YET + + - id: TASTY_CHEESE + material: honeycomb + rarity: COMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Tasty Cheese + - id: NOT_FINISHED_YET + + - id: JELLY + material: slime_ball + rarity: COMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Jelly + - id: NOT_FINISHED_YET + + - id: BURROWING_SPORES + material: brown_mushroom + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Burrowing Spores + - id: NOT_FINISHED_YET + + - id: IRIDIUM + material: prismarine_crystals + rarity: EPIC + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Iridium + - id: NOT_FINISHED_YET + + - id: BEADY_EYES + material: spider_eye + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Beady Eyes + - id: NOT_FINISHED_YET + + - id: CLIPPED_WINGS + material: feather + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Clipped Wings + - id: NOT_FINISHED_YET + + - id: BOOKWORMS_FAVORITE_BOOK + material: book + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Bookworm's Favorite Book + - id: NOT_FINISHED_YET + + - id: WRIGGLING_LARVA + material: string + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Wriggling Larva + - id: NOT_FINISHED_YET + + - id: MANTID_CLAW + material: rabbit_foot + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Mantid Claw + - id: NOT_FINISHED_YET + + - id: FIRE_IN_A_BOTTLE + material: experience_bottle + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Fire in a Bottle + - id: NOT_FINISHED_YET + + - id: DUNG_DYE + material: brown_dye + rarity: EPIC + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Dung Dye + - id: NOT_FINISHED_YET + + - id: SQUEAKY_TOY + material: rabbit_foot + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Squeaky Toy + - id: NOT_FINISHED_YET + + - id: SQUEAKY_MOUSEMAT + material: gray_carpet + rarity: EPIC + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Squeaky Mousemat + - id: NOT_FINISHED_YET + + - id: ENCHANTED_SUNFLOWER + material: sunflower + rarity: UNCOMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Sunflower + - id: NOT_FINISHED_YET + + - id: COMPACTED_SUNFLOWER + material: yellow_wool + rarity: RARE + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Compacted Sunflower + - id: NOT_FINISHED_YET + + - id: ENCHANTED_MOONFLOWER + material: white_dye + rarity: UNCOMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Moonflower + - id: NOT_FINISHED_YET + + - id: COMPACTED_MOONFLOWER + material: white_wool + rarity: RARE + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Compacted Moonflower + - id: NOT_FINISHED_YET + + - id: ENCHANTED_WILD_ROSE + material: poppy + rarity: UNCOMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Wild Rose + - id: NOT_FINISHED_YET + + - id: COMPACTED_WILD_ROSE + material: red_wool + rarity: RARE + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Compacted Wild Rose + - id: NOT_FINISHED_YET + + - id: VERMIN_VAPORIZER_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Vermin Vaporizer Chip + - id: NOT_FINISHED_YET + + - id: SYNTHESIS_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Synthesis Chip + - id: NOT_FINISHED_YET + + - id: SOWLEDGE_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Sowledge Chip + - id: NOT_FINISHED_YET + + - id: MECHAMIND_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Mechamind Chip + - id: SKULL_HEAD + texture: 560aa469cc6b667dbcbfdc63e827b7c05ca7726af8a178a4aa2e8ffa2690e843 + - id: NOT_FINISHED_YET + + - id: HYPERCHARGE_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Hypercharge Chip + - id: NOT_FINISHED_YET + + - id: EVERGREEN_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Evergreen Chip + - id: NOT_FINISHED_YET + + - id: OVERDRIVE_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Overdrive Chip + - id: SKULL_HEAD + texture: 115f8b48be9a2c3539af2b7c4026fb27a3dc3580cba74cc1f3ec8beda9a91af5 + - id: NOT_FINISHED_YET + + - id: CROPSHOT_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Cropshot Chip + - id: SKULL_HEAD + texture: e85b6f92f03867f835d3179a42557b4c4bdd3545c9a8e9285bdd32dab464d08f + - id: NOT_FINISHED_YET + + - id: QUICKDRAW_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Quickdraw Chip + - id: NOT_FINISHED_YET + + - id: RAREFINDER_GARDEN_CHIP + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Rarefinder Chip + - id: NOT_FINISHED_YET + + - id: SUNDER_I + material: enchanted_book + rarity: COMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Book (Sunder I) + - id: NOT_FINISHED_YET + + - id: GREEN_THUMB_I + material: enchanted_book + rarity: COMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Book (Green Thumb I) + - id: NOT_FINISHED_YET + + - id: DEDICATION_I + material: enchanted_book + rarity: COMMON + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Book (Dedication I) + - id: NOT_FINISHED_YET + + - id: SUNDER_VI_BOOK + material: enchanted_book + rarity: RARE + components: + - id: ENCHANTED + - id: CUSTOM_DISPLAY_NAME + display_name: Enchanted Book (Sunder VI) + - id: NOT_FINISHED_YET diff --git a/configuration/skyblock/items/farming/garden_visitors.yml b/configuration/skyblock/items/farming/garden_visitors.yml new file mode 100644 index 0000000000..b464e9eb57 --- /dev/null +++ b/configuration/skyblock/items/farming/garden_visitors.yml @@ -0,0 +1,136 @@ +items: + - id: FLOWERING_BOUQUET + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Flowering Bouquet + - id: NOT_FINISHED_YET + + - id: FRUIT_BOWL + material: bowl + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Fruit Bowl + - id: NOT_FINISHED_YET + + - id: OVERGROWN_GRASS + material: grass_block + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Overgrown Grass + - id: NOT_FINISHED_YET + + - id: GREEN_BANDANA + material: lime_banner + rarity: EPIC + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Green Bandana + - id: NOT_FINISHED_YET + + - id: COPPER_DYE + material: orange_dye + rarity: SPECIAL + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Copper Dye + - id: NOT_FINISHED_YET + + - id: FINE_FLOUR + material: player_head + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Fine Flour + - id: NOT_FINISHED_YET + + - id: CARNIVAL_TICKET + material: name_tag + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Carnival Ticket + - id: NOT_FINISHED_YET + + - id: CROPIE + material: player_head + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Cropie + - id: NOT_FINISHED_YET + + - id: SQUASH + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Squash + - id: NOT_FINISHED_YET + + - id: FERMENTO + material: player_head + rarity: EPIC + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Fermento + - id: NOT_FINISHED_YET + + - id: FARMING_EXP_BOOST_UNCOMMON + material: iron_hoe + rarity: UNCOMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Farming Exp Boost + - id: NOT_FINISHED_YET + + - id: HARVEST_HARBINGER_POTION + material: potion + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Harvest Harbinger V Potion + - id: NOT_FINISHED_YET + + - id: MYSTERIOUS_CROP + material: dead_bush + rarity: COMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Mysterious Crop + - id: NOT_FINISHED_YET + + - id: PET_CAKE + material: cake + rarity: COMMON + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Pet Cake + - id: NOT_FINISHED_YET + + - id: ARACHNE_FRAGMENT + material: player_head + rarity: RARE + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Arachne Fragment + - id: NOT_FINISHED_YET + + - id: SPACE_HELMET + material: player_head + rarity: SPECIAL + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Space Helmet + - id: NOT_FINISHED_YET + + - id: WILD_STRAWBERRY_DYE + material: player_head + rarity: LEGENDARY + components: + - id: CUSTOM_DISPLAY_NAME + display_name: Wild Strawberry Dye + - id: NOT_FINISHED_YET diff --git a/configuration/skyblock/items/miscellaneous/miscellaneous.yml b/configuration/skyblock/items/miscellaneous/miscellaneous.yml index 81c2f97c0c..dfa6dbfb83 100644 --- a/configuration/skyblock/items/miscellaneous/miscellaneous.yml +++ b/configuration/skyblock/items/miscellaneous/miscellaneous.yml @@ -5,6 +5,12 @@ items: components: - id: INTERACTABLE handler_id: MOVE_JERRY_INTERACT + - id: MOVE_SAM + material: villager_spawn_egg + rarity: COMMON + components: + - id: INTERACTABLE + handler_id: MOVE_SAM_INTERACT - id: SKYBLOCK_MENU material: nether_star rarity: COMMON diff --git a/garden_static_code_snippets/GUIAnita.java b/garden_static_code_snippets/GUIAnita.java new file mode 100644 index 0000000000..65e72b5052 --- /dev/null +++ b/garden_static_code_snippets/GUIAnita.java @@ -0,0 +1,333 @@ +public class GUIAnita extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Anita", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(10, ItemStackCreator.getStack( + "§aInfiniDirt™ Wand", + Material.STICK, + 1, + "§6Ability: Place Dirt §e§lRIGHT CLICK", + "§7Place a dirt block.", + "§7Costs §61 coin§7.", + "", + "§7Can be used within the Builder's Wand!", + "", + "§a§lUNCOMMON", + "", + "§7Cost", + "§aJacob's Ticket", + "", + "§eClick to trade!" + )); + layout.slot(11, ItemStackCreator.getStack( + "§9Prismapump §8x4", + Material.DARK_PRISMARINE, + 4, + "§6Ability: Pipeline §e§lRIGHT CLICK", + "§7When used, pumps water in the direction the", + "§7player was facing.", + "", + "§9§lRARE", + "", + "§7Cost", + "§cBronze medal", + "§aJacob's Ticket §8x2", + "", + "§eClick to trade!" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aHoe of Great Tilling", + Material.STONE_HOE, + 1, + "§7Tills a §93x3 §7area of farmland at a time.", + "", + "§8This item can be reforged!", + "§a§lUNCOMMON HOE", + "", + "§7Cost", + "§cBronze medal", + "§aJacob's Ticket §8x5", + "", + "§eClick to trade!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§9Hoe of Greater Tilling", + Material.DIAMOND_HOE, + 1, + "§7Tills a §95x5 §7area of farmland at a time.", + "", + "§8This item can be reforged!", + "§9§lRARE HOE", + "", + "§7Cost", + "§fSilver medal", + "§aJacob's Ticket §8x10", + "", + "§eClick to trade!" + )); + layout.slot(14, ItemStackCreator.getStack( + "§5Hoe of Greatest Tilling", + Material.DIAMOND_HOE, + 1, + "§7Tills a row of farmland. The perfect", + "§7accessory to the §5Basket of Seeds§7!", + "", + "§8This item can be reforged!", + "§5§lEPIC HOE", + "", + "§7Cost", + "§fSilver medal§8 x2", + "§aJacob's Ticket §8x20", + "", + "§eClick to trade!" + )); + layout.slot(15, ItemStackCreator.getStackHead( + "§5Basket of Seeds", + "7a6bf916e28ccb80b4ebfacf98686ad6af7c4fb257e57a8cb78c71d19dccb2", + 1, + "", + "§6Ability: Seed Storage §e§lLEFT CLICK", + "§7Place seeds in the basket.", + "", + "§6Ability: Farmer's Delight §e§lRIGHT CLICK", + "§7Automatically seed a row of farmland.", + "", + "§5§lEPIC", + "", + "§7Cost", + "§fSilver medal§8 x2", + "§aJacob's Ticket §8x30", + "", + "§eClick to trade!" + )); + layout.slot(16, ItemStackCreator.getStackHead( + "§9Nether Wart Pouch", + "85e24b8c2d20f9e23bca52d94fbd820aa0744591acb0359183312e43a287e5b0", + 1, + "", + "§6Ability: Nether Wart Storage §e§lLEFT CLICK", + "§7Place nether wart in the pouch.", + "", + "§6Ability: Alchemist's Bliss §e§lRIGHT CLICK", + "§7Automatically plant nether wart on a", + "§7row of soul sand.", + "", + "§9§lRARE", + "", + "§7Cost", + "§fSilver medal§8 x2", + "§aJacob's Ticket §8x30", + "", + "§eClick to trade!" + )); + layout.slot(19, ItemStackCreator.getStack( + "§aEnchanted Book", + Material.ENCHANTED_BOOK, + 1, + "§9Delicate V", + "§7Avoids breaking stems and baby", + "§7crops.", + "", + "§7Applicable on: §9Axe§7, §9Hoe", + "§7Apply Cost: §350 Exp Levels", + "", + "§e▲ §7Delicate cannot be combined!", + "§7Use this on an item in an Anvil to", + "§7apply it!", + "", + "§a§lUNCOMMON", + "", + "§7Cost", + "§fSilver medal§8 x2", + "§aJacob's Ticket §8x32", + "", + "§eClick to trade!" + )); + layout.slot(20, ItemStackCreator.getStack( + "§e+1 Farming Level Cap", + Material.HAY_BLOCK, + 1, + "§7Raise your Farming Skill level cap", + "§7by one, up to §f+10§7.", + "", + "§7Your cap: §eFarming L", + "", + "§7Requirement", + "§c✖ §7Win §6§lGOLD §7in §e1 §7collection! (§c0§7/§e1§7)", + "", + "§7Cost", + "§6Gold medal", + "", + "§eClick to trade!" + )); + layout.slot(21, ItemStackCreator.getStack( + "§eExtra Farming Fortune", + Material.WHEAT, + 1, + "§7Permanently gain §6+4☘ Farming", + "§6Fortune §7per tier.", + "", + "§7Cost", + "§6Gold medal", + "", + "§eClick to trade!" + )); + layout.slot(22, ItemStackCreator.getStack( + "§ePersonal Bests", + Material.PAPER, + 1, + "§7Beating your §6Personal Best §7is great!", + "§7But what if it also gave you §6☘", + "§6Farming Fortune§7?!", + "", + "§7Cost", + "§6Gold medal§8 x2", + "§aJacob's Ticket §8x64", + "", + "§eClick to trade!" + )); + layout.slot(23, ItemStackCreator.getStackHead( + "§fAnita's Talisman", + "bb8ec57b37fdf093fe66efe2ac070a8f5181949970a4458d56ec9701eded8cff", + 1, + "§7Grants a random §6+5☘ Farming", + "§6Fortune §7stat during §eJacob's Farming", + "§eContest§7.", + "", + "§8Works while in Accessory Bag!", + "§f§lCOMMON ACCESSORY", + "", + "§7Cost", + "§cBronze medal§8 x2", + "§aJacob's Ticket §8x32", + "", + "§eClick to trade!" + )); + layout.slot(24, ItemStackCreator.getStackHead( + "§aAnita's Ring", + "59a1035bc6f00fddc8e0291c38319408babc84ae10648cb5258ed8b55d60e0c3", + 1, + "§7Grants a random §6+15☘ Farming", + "§6Fortune §7stat during §eJacob's Farming", + "§eContest§7.", + "", + "§8Works while in Accessory Bag!", + "§a§lUNCOMMON ACCESSORY", + "", + "§7Cost", + "§fSilver medal§8 x2", + "§aJacob's Ticket §8x64", + "§fAnita's Talisman", + "", + "§eClick to trade!" + )); + layout.slot(25, ItemStackCreator.getStackHead( + "§9Anita's Artifact", + "8feabdadd5f593771fa23c94fc6816091917371fd5a1c74723e716210d6e6efb", + 1, + "§7Grants a random §6+25☘ Farming", + "§6Fortune §7stat during §eJacob's Farming", + "§eContest§7.", + "", + "§8Works while in Accessory Bag!", + "§9§lRARE ACCESSORY", + "", + "§7Cost", + "§6Gold medal§8 x2", + "§aJacob's Ticket §8x100", + "§aAnita's Ring", + "", + "§eClick to trade!" + )); + layout.slot(28, ItemStackCreator.getStackHead( + "§9Mechamind Chip", + "560aa469cc6b667dbcbfdc63e827b7c05ca7726af8a178a4aa2e8ffa2690e843", + 1, + "§8Garden Chip", + "", + "§6Ability: Mechamind ", + "§7Grants §a1.5% §7more §eFarming Tool", + "§7Experience per §9Chip §7Level!", + "", + "§7Redeem Chips to increase their rarity!", + "", + "§eRight-Click to redeem!", + "§eShift Right-Click to redeem all!", + "", + "§9§lRARE GARDEN CHIP", + "", + "§7Cost", + "§6Gold medal", + "", + "§eClick to trade!" + )); + layout.slot(29, ItemStackCreator.getStackHead( + "§9Overdrive Chip", + "115f8b48be9a2c3539af2b7c4026fb27a3dc3580cba74cc1f3ec8beda9a91af5", + 1, + "§8Garden Chip", + "", + "§6Ability: Overdrive ", + "§7Grants §6+5☘ Crop Fortune §7for the", + "§7active crop during §eJacob's Farming", + "§eContest §7per §9Chip §7Level!", + "", + "§7Redeem Chips to increase their rarity!", + "", + "§eRight-Click to redeem!", + "§eShift Right-Click to redeem all!", + "", + "§9§lRARE GARDEN CHIP", + "", + "§7Cost", + "§6Gold medal§8 x2", + "", + "§eClick to trade!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aPersonal Bests", + Material.PAPER, + 1, + "§7The higher your §6Personal Bests §7are", + "§7from participating in §eJacob's Farming", + "§eContest§7, the more §6☘ Farming Fortune", + "§7you'll obtain for that crop!", + "", + "§cPurchase the perk in Anita's Shop to", + "§cunlock this menu!" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aMedal Trades", + Material.POWERED_RAIL, + 1, + "§7Convert and sell your medals.", + "", + "§eClick to open!" + )); + layout.slot(51, ItemStackCreator.getStack( + "§eUnique Brackets Reached", + Material.GOLD_INGOT, + 1, + "§c◌§f◌§6◌ §cWheat", + "§c◌§f◌§6◌ §cCarrot", + "§c●§f◌§6◌ §cPotato", + "§c◌§f◌§6◌ §cPumpkin", + "§c◌§f◌§6◌ §cMelon Slice", + "§c◌§f◌§6◌ §cMushroom", + "§c◌§f◌§6◌ §cCactus", + "§c◌§f◌§6◌ §cSugar Cane", + "§c◌§f◌§6◌ §cNether Wart", + "§c◌§f◌§6◌ §cCocoa Beans", + "§c◌§f◌§6◌ §cSunflower", + "§c◌§f◌§6◌ §cMoonflower", + "§c◌§f◌§6◌ §cWild Rose" + )); + } +} diff --git a/garden_static_code_snippets/GUIBarnSkins.java b/garden_static_code_snippets/GUIBarnSkins.java new file mode 100644 index 0000000000..025caf6e12 --- /dev/null +++ b/garden_static_code_snippets/GUIBarnSkins.java @@ -0,0 +1,79 @@ +public class GUIBarnSkins extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Barn Skins", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(10, ItemStackCreator.getStack( + "§fDefault", + Material.DARK_OAK_PLANKS, + 1, + "§7Select this skin for your Barn!", + "", + "§f§lCOMMON COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(11, ItemStackCreator.getStack( + "§aMedieval", + Material.SPRUCE_LOG, + 1, + "§7Select this skin for your Barn!", + "", + "§a§lUNCOMMON COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aRed", + Material.QUARTZ_BLOCK, + 1, + "§7Select this skin for your Barn!", + "", + "§a§lUNCOMMON COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aSunny", + Material.RED_SANDSTONE, + 1, + "§7Select this skin for your Barn!", + "", + "§a§lUNCOMMON COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(14, ItemStackCreator.getStack( + "§9Cabin", + Material.LIGHT_BLUE_TERRACOTTA, + 1, + "§7Select this skin for your Barn!", + "", + "§9§lRARE COSMETIC", + "", + "§aSELECTED" + )); + layout.slot(15, ItemStackCreator.getStackHead( + "§6Chocolate Factory", + "af90da40c557af4ac01d39b6733e204c74ae9fee8c2bc40be1fd4f28f837d52", + 1, + "§7Select this skin for your Barn!", + "", + "§6§lLEGENDARY COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Skins" + )); + } +} diff --git a/garden_static_code_snippets/GUICarpenter.java b/garden_static_code_snippets/GUICarpenter.java new file mode 100644 index 0000000000..a5cb1e587a --- /dev/null +++ b/garden_static_code_snippets/GUICarpenter.java @@ -0,0 +1,45 @@ +public class GUICarpenter extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Carpenter", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(13, ItemStackCreator.getStackHead( + "§9Carpenter", + "cc8d2a5af975c53cf081b531566148bae366141314b35aa5726e97cc97ef118b", + 1, + "§9§lRARE", + "", + "§7Times Visited: §a4", + "§7Offers Accepted: §a3" + )); + layout.slot(29, ItemStackCreator.getStack( + "§aAccept Offer", + Material.GREEN_TERRACOTTA, + 1, + "§7Items Required:", + " §9Enchanted Melon §8x7", + "", + "§7Rewards:", + " §8+§34.7k §7Farming XP", + " §8+§230 §7Garden Experience", + " §8+§c58 Copper", + "", + "§cMissing items to accept!" + )); + layout.slot(33, ItemStackCreator.getStack( + "§cRefuse Offer", + Material.RED_TERRACOTTA, + 1, + "§9Carpenter §7will leave your §aGarden", + "§7and maybe come back later.", + "", + "§eClick to refuse!" + )); + } +} diff --git a/garden_static_code_snippets/GUIComposter.java b/garden_static_code_snippets/GUIComposter.java new file mode 100644 index 0000000000..388fe3fb91 --- /dev/null +++ b/garden_static_code_snippets/GUIComposter.java @@ -0,0 +1,163 @@ +public class GUIComposter extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Composter", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(1, ItemStackCreator.getStack( + "§eOrganic Matter", + Material.RED_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k" + )); + layout.slot(7, ItemStackCreator.getStack( + "§2Fuel", + Material.RED_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k" + )); + layout.slot(10, ItemStackCreator.getStack( + "§eOrganic Matter", + Material.RED_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k" + )); + layout.slot(13, ItemStackCreator.getStack( + "§eCollect Compost", + Material.RED_TERRACOTTA, + 1, + "§7Compost Available: §a0" + )); + layout.slot(16, ItemStackCreator.getStack( + "§2Fuel", + Material.YELLOW_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k" + )); + layout.slot(19, ItemStackCreator.getStack( + "§eOrganic Matter", + Material.RED_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k" + )); + layout.slot(22, ItemStackCreator.getStack( + "§aComposter Upgrades", + Material.HOPPER, + 1, + "§7Upgrade your composter to increase", + "§7your compost production.", + "", + "§eClick to view upgrades!" + )); + layout.slot(25, ItemStackCreator.getStack( + "§2Fuel", + Material.LIME_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k" + )); + layout.slot(28, ItemStackCreator.getStack( + "§eOrganic Matter", + Material.RED_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k" + )); + layout.slot(34, ItemStackCreator.getStack( + "§2Fuel", + Material.LIME_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k" + )); + layout.slot(37, ItemStackCreator.getStack( + "§eOrganic Matter", + Material.YELLOW_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k" + )); + layout.slot(39, ItemStackCreator.getStack( + "§aInsert Crops from Sacks", + Material.CAULDRON, + 1, + "§7Grab as many crops that will fit into", + "§7the composter from your sacks.", + "", + "§7In your sacks: §e33.9k Organic Matter", + "", + "§eLeft-click to grab from sacks!" + )); + layout.slot(41, ItemStackCreator.getStack( + "§aInsert Fuel from Sacks", + Material.CAULDRON, + 1, + "§7Grab as much fuel that will fit into", + "§7the composter from your sacks.", + "", + "§7In your sacks:", + " §a5§7x §aOil Barrel §7worth §250k Fuel", + "", + "§7Totalling §250k Fuel§7.", + "", + "§eLeft-click to grab from sacks!" + )); + layout.slot(43, ItemStackCreator.getStack( + "§2Fuel", + Material.LIME_STAINED_GLASS_PANE, + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k" + )); + layout.slot(46, ItemStackCreator.getStack( + "§eCrop Meter", + Material.POTATO, + 1, + "§2§l§m §f§l§m §e3,876.5§6/§e40k", + "", + "§7Fill your composter with §acrops§7, like", + "§fWheat §7or §aEnchanted Potato§7, to turn", + "§7them into §eOrganic Matter§7. Organic", + "§7Matter is used to make §6Compost§7.", + "", + "§7The composter must have §b4,000", + "§7organic matter stored to start", + "§7making compost." + )); + layout.slot(48, ItemStackCreator.getStackHead( + "§aInsert Crops from Inventory", + "ef835b8941fe319931749b87fe8e84c5d1f4a271b5fbce5e700a60004d881f79", + 1, + "§7Grab as many crops that will fit into", + "§7the composter from your inventory.", + "", + "§7In your inventory: §cNo Organic Matter", + "", + "§cNo crops in your inventory!" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aInsert Fuel from Inventory", + Material.GREEN_DYE, + 1, + "§7Grab as much fuel that will fit into", + "§7the composter from your inventory.", + "", + "§7In your inventory: §cNo Fuel", + "", + "§cNo fuel in your inventory!" + )); + layout.slot(52, ItemStackCreator.getStackHead( + "§2Fuel Meter", + "d5d2750595477ecc13869580b12ffc3b13fc2b3ac3e5035ecfc9aafa036722a2", + 1, + "§2§l§m §f§l§m §e79,000§6/§e100k", + "", + "§7Fill your composter with §2machine fuel§7,", + "§7like §9Biofuel§7 to power the composter", + "§7to turn Organic Matter into §6Compost§7.", + "", + "§7The composter must have §22,000♢", + "§2Fuel §7stored to start making compost." + )); + } +} diff --git a/garden_static_code_snippets/GUIConfigurePlots.java b/garden_static_code_snippets/GUIConfigurePlots.java new file mode 100644 index 0000000000..18667f9f9b --- /dev/null +++ b/garden_static_code_snippets/GUIConfigurePlots.java @@ -0,0 +1,341 @@ +public class GUIConfigurePlots extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Configure Plots", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(2, ItemStackCreator.getStack( + "§ePlot §7- §b21", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 7", + "", + "§7Cost:", + "§9Compost Bundle §8x8", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(3, ItemStackCreator.getStack( + "§aPlot §7- §b13", + Material.CARVED_PUMPKIN, + 1, + "§7Preset: §aPumpkin", + "§4§lൠ §cThis plot has §21 ൠ Pest§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(4, ItemStackCreator.getStack( + "§ePlot §7- §b9", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(5, ItemStackCreator.getStack( + "§ePlot §7- §b14", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(6, ItemStackCreator.getStack( + "§ePlot §7- §b22", + Material.RED_STAINED_GLASS_PANE, + 1, + "§7Requirement", + "§a✔ Garden Level 7", + "", + "§7Cost:", + "§9Compost Bundle §8x8", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need to unlock an adjacent plot", + "§cfirst!" + )); + layout.slot(11, ItemStackCreator.getStack( + "§ePlot §7- §b15", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aPlot §7- §b5", + Material.CARVED_PUMPKIN, + 1, + "§7Preset: §aPumpkin", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aPlot §7- §b1", + Material.SUGAR_CANE, + 1, + "§7Preset: §aSugar Cane", + "§4§lൠ §cThis plot has §21 ൠ Pest§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(14, ItemStackCreator.getStack( + "§aPlot §7- §b6", + Material.ROSE_BUSH, + 1, + "§7Preset: §aWild Rose", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§ePlot §7- §b16", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(20, ItemStackCreator.getStack( + "§ePlot §7- §b10", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(21, ItemStackCreator.getStack( + "§aPlot §7- §b2", + Material.CARVED_PUMPKIN, + 1, + "§7Preset: §aPumpkin", + "§4§lൠ §cThis plot has §23 ൠ Pests§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(22, ItemStackCreator.getStack( + "§aThe Barn", + Material.LIGHT_BLUE_TERRACOTTA, + 1, + "", + "§eRight-click to teleport to this plot!" + )); + layout.slot(23, ItemStackCreator.getStack( + "§aPlot §7- §b3", + Material.WHITE_STAINED_GLASS, + 1, + "§7Greenhouse Plot", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(24, ItemStackCreator.getStack( + "§ePlot §7- §b11", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(29, ItemStackCreator.getStack( + "§ePlot §7- §b17", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aPlot §7- §b7", + Material.CARVED_PUMPKIN, + 1, + "§7Preset: §aPumpkin", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(31, ItemStackCreator.getStack( + "§aPlot §7- §b4", + Material.POTATO, + 1, + "§7Preset: §aPotato", + "§4§lൠ §cThis plot has §21 ൠ Pest§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(32, ItemStackCreator.getStack( + "§aPlot §7- §b8", + Material.RED_MUSHROOM, + 1, + "§7Preset: §aMushroom", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(33, ItemStackCreator.getStack( + "§ePlot §7- §b18", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(38, ItemStackCreator.getStack( + "§ePlot §7- §b23", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 7", + "", + "§7Cost:", + "§9Compost Bundle §8x8", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(39, ItemStackCreator.getStack( + "§aPlot §7- §b19", + Material.CARVED_PUMPKIN, + 1, + "§7Preset: §aPumpkin", + "§4§lൠ §cThis plot has §21 ൠ Pest§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(40, ItemStackCreator.getStack( + "§aPlot §7- §b12", + Material.POTATO, + 1, + "§7Preset: §aPotato", + "§4§lൠ §cThis plot has §21 ൠ Pest§c!", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + )); + layout.slot(41, ItemStackCreator.getStack( + "§ePlot §7- §b20", + Material.OAK_BUTTON, + 1, + "§7Requirement", + "§a✔ Garden Level 5", + "", + "§7Cost:", + "§9Compost Bundle §8x1", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + )); + layout.slot(42, ItemStackCreator.getStack( + "§ePlot §7- §b24", + Material.RED_STAINED_GLASS_PANE, + 1, + "§7Requirement", + "§a✔ Garden Level 7", + "", + "§7Cost:", + "§9Compost Bundle §8x8", + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need to unlock an adjacent plot", + "§cfirst!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUICropMilestones.java b/garden_static_code_snippets/GUICropMilestones.java new file mode 100644 index 0000000000..dae0a48ca9 --- /dev/null +++ b/garden_static_code_snippets/GUICropMilestones.java @@ -0,0 +1,250 @@ +public class GUICropMilestones extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Crop Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(11, ItemStackCreator.getStack( + "§aWheat XVIII", + Material.WHEAT, + 1, + "§7Harvest §fWheat §7on your Garden to", + "§7increase your §fWheat §7tier!", + "", + "§7Total: §a469,212", + "", + "§7Progress to Tier XIX: §e86.2%", + "§2§l§m §f§l§m §e150,802§6/§e175k", + "", + "§7Rewards:", + " §8+§2190 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aCarrot XVII", + Material.CARROT, + 1, + "§7Harvest §fCarrot §7on your Garden to", + "§7increase your §fCarrot §7tier!", + "", + "§7Total: §a1,039,360", + "", + "§7Progress to Tier XVIII: §e84.2%", + "§2§l§m §f§l§m §e294,860§6/§e350k", + "", + "§7Rewards:", + " §8+§2180 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aPotato XXIII", + Material.POTATO, + 1, + "§7Harvest §fPotato §7on your Garden to", + "§7increase your §fPotato §7tier!", + "", + "§7Total: §a8,149,777", + "", + "§7Progress to Tier XXIV: §e59.3%", + "§2§l§m §f§l§m §e1,305,277§6/§e2.2M", + "", + "§7Progress to Tier XLVI: §e12.3%", + "§2§l§m §f§l§m §e8,149,777§6/§e66.2M", + "", + "§7Rewards:", + " §8+§2240 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(14, ItemStackCreator.getStack( + "§aPumpkin XXV", + Material.CARVED_PUMPKIN, + 1, + "§7Harvest §fPumpkin §7on your Garden to", + "§7increase your §fPumpkin §7tier!", + "", + "§7Total: §a3,624,486", + "", + "§7Progress to Tier XXVI: §e25.8%", + "§2§l§m §f§l§m §e206,076§6/§e800k", + "", + "§7Progress to Tier XLVI: §e17.9%", + "§2§l§m §f§l§m §e3,624,486§6/§e20.2M", + "", + "§7Rewards:", + " §8+§2260 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(15, ItemStackCreator.getStack( + "§aSugar Cane XVII", + Material.SUGAR_CANE, + 1, + "§7Harvest §fSugar Cane §7on your Garden", + "§7to increase your §fSugar Cane §7tier!", + "", + "§7Total: §a556,707", + "", + "§7Progress to Tier XVIII: §e59.9%", + "§2§l§m §f§l§m §e119,887§6/§e200k", + "", + "§7Rewards:", + " §8+§2180 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(20, ItemStackCreator.getStack( + "§aMelon Slice XVI", + Material.MELON_SLICE, + 1, + "§7Harvest §fMelon Slice §7on your Garden", + "§7to increase your §fMelon Slice §7tier!", + "", + "§7Total: §a844,640", + "", + "§7Progress to Tier XVII: §e34%", + "§2§l§m §f§l§m §e127,590§6/§e375k", + "", + "§7Rewards:", + " §8+§2170 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(21, ItemStackCreator.getStack( + "§aCactus XVIII", + Material.CACTUS, + 1, + "§7Harvest §fCactus §7on your Garden to", + "§7increase your §fCactus §7tier!", + "", + "§7Total: §a678,335", + "", + "§7Progress to Tier XIX: §e11.9%", + "§2§l§m §f§l§m §e41,515§6/§e350k", + "", + "§7Rewards:", + " §8+§2190 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(22, ItemStackCreator.getStack( + "§aCocoa Beans XVI", + Material.COCOA_BEANS, + 1, + "§7Harvest §fCocoa Beans §7on your", + "§7Garden to increase your §fCocoa", + "§fBeans §7tier!", + "", + "§7Total: §a485,440", + "", + "§7Progress to Tier XVII: §e35%", + "§2§l§m §f§l§m §e69,950§6/§e200k", + "", + "§7Rewards:", + " §8+§2170 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(23, ItemStackCreator.getStack( + "§aMushroom XXI", + Material.RED_MUSHROOM, + 1, + "§7Harvest §fMushroom §7on your Garden", + "§7to increase your §fMushroom §7tier!", + "", + "§7Total: §a1,242,256", + "", + "§7Progress to Tier XXII: §e43.5%", + "§2§l§m §f§l§m §e173,846§6/§e400k", + "", + "§7Progress to Tier XLVI: §e6.1%", + "§2§l§m §f§l§m §e1,242,256§6/§e20.2M", + "", + "§7Rewards:", + " §8+§2220 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(24, ItemStackCreator.getStack( + "§aNether Wart XVII", + Material.NETHER_WART, + 1, + "§7Harvest §fNether Wart §7on your Garden", + "§7to increase your §fNether Wart §7tier!", + "", + "§7Total: §a649,280", + "", + "§7Progress to Tier XVIII: §e11.3%", + "§2§l§m §f§l§m §e33,790§6/§e300k", + "", + "§7Rewards:", + " §8+§2180 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aMoonflower XVI", + Material.BLUE_ORCHID, + 1, + "§7Harvest §fMoonflower §7on your Garden", + "§7to increase your §fMoonflower §7tier!", + "", + "§7Total: §a198,560", + "", + "§7Progress to Tier XVII: §e73.1%", + "§2§l§m §f§l§m §e54,800§6/§e75k", + "", + "§7Rewards:", + " §8+§2170 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(31, ItemStackCreator.getStack( + "§aSunflower XVII", + Material.SUNFLOWER, + 1, + "§7Harvest §fSunflower §7on your Garden", + "§7to increase your §fSunflower §7tier!", + "", + "§7Total: §a253,440", + "", + "§7Progress to Tier XVIII: §e35%", + "§2§l§m §f§l§m §e35,030§6/§e100k", + "", + "§7Rewards:", + " §8+§2180 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(32, ItemStackCreator.getStack( + "§aWild Rose XVII", + Material.ROSE_BUSH, + 1, + "§7Harvest §fWild Rose §7on your Garden to", + "§7increase your §fWild Rose §7tier!", + "", + "§7Total: §a633,416", + "", + "§7Progress to Tier XVIII: §e98.3%", + "§2§l§m §e196,596§6/§e200k", + "", + "§7Rewards:", + " §8+§2180 §7Garden Experience", + " §8+§350,000 §7Farming Experience", + " §8+§b1 SkyBlock XP" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Milestones" + )); + } +} diff --git a/garden_static_code_snippets/GUICropUpgrades.java b/garden_static_code_snippets/GUICropUpgrades.java new file mode 100644 index 0000000000..70f26562fc --- /dev/null +++ b/garden_static_code_snippets/GUICropUpgrades.java @@ -0,0 +1,178 @@ +public class GUICropUpgrades extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Crop Upgrades", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(11, ItemStackCreator.getStack( + "§aWheat", + Material.WHEAT, + 1, + "§7Upgrade your §aWheat §7tier to increase", + "§7your §6☘ Wheat Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Wheat Fortune: §6+15☘", + "", + "§eClick to view!" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aCarrot", + Material.CARROT, + 1, + "§7Upgrade your §aCarrot §7tier to", + "§7increase your §6☘ Carrot Fortune§7.", + "", + "§7Current Tier: §e1§7/§a9", + "§7Carrot Fortune: §6+5☘", + "", + "§eClick to view!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aPotato", + Material.POTATO, + 1, + "§7Upgrade your §aPotato §7tier to", + "§7increase your §6☘ Potato Fortune§7.", + "", + "§7Current Tier: §e6§7/§a9", + "§7Potato Fortune: §6+30☘", + "", + "§eClick to view!" + )); + layout.slot(14, ItemStackCreator.getStack( + "§aPumpkin", + Material.CARVED_PUMPKIN, + 1, + "§7Upgrade your §aPumpkin §7tier to", + "§7increase your §6☘ Pumpkin Fortune§7.", + "", + "§7Current Tier: §e6§7/§a9", + "§7Pumpkin Fortune: §6+30☘", + "", + "§eClick to view!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§aSugar Cane", + Material.SUGAR_CANE, + 1, + "§7Upgrade your §aSugar Cane §7tier to", + "§7increase your §6☘ Sugar Cane", + "§6Fortune§7.", + "", + "§7Current Tier: §e5§7/§a9", + "§7Sugar Cane Fortune: §6+25☘", + "", + "§eClick to view!" + )); + layout.slot(20, ItemStackCreator.getStack( + "§aMelon Slice", + Material.MELON_SLICE, + 1, + "§7Upgrade your §aMelon Slice §7tier to", + "§7increase your §6☘ Melon Slice Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Melon Slice Fortune: §6+15☘", + "", + "§eClick to view!" + )); + layout.slot(21, ItemStackCreator.getStack( + "§aCactus", + Material.CACTUS, + 1, + "§7Upgrade your §aCactus §7tier to", + "§7increase your §6☘ Cactus Fortune§7.", + "", + "§7Current Tier: §e2§7/§a9", + "§7Cactus Fortune: §6+10☘", + "", + "§eClick to view!" + )); + layout.slot(22, ItemStackCreator.getStack( + "§aCocoa Beans", + Material.COCOA_BEANS, + 1, + "§7Upgrade your §aCocoa Beans §7tier to", + "§7increase your §6☘ Cocoa Beans", + "§6Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Cocoa Beans Fortune: §6+15☘", + "", + "§eClick to view!" + )); + layout.slot(23, ItemStackCreator.getStack( + "§aMushroom", + Material.RED_MUSHROOM, + 1, + "§7Upgrade your §aMushroom §7tier to", + "§7increase your §6☘ Mushroom Fortune§7.", + "", + "§7Current Tier: §e2§7/§a9", + "§7Mushroom Fortune: §6+10☘", + "", + "§eClick to view!" + )); + layout.slot(24, ItemStackCreator.getStack( + "§aNether Wart", + Material.NETHER_WART, + 1, + "§7Upgrade your §aNether Wart §7tier to", + "§7increase your §6☘ Nether Wart", + "§6Fortune§7.", + "", + "§7Current Tier: §e2§7/§a9", + "§7Nether Wart Fortune: §6+10☘", + "", + "§eClick to view!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aSunflower", + Material.SUNFLOWER, + 1, + "§7Upgrade your §aSunflower §7tier to", + "§7increase your §6☘ Sunflower Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Sunflower Fortune: §6+15☘", + "", + "§eClick to view!" + )); + layout.slot(31, ItemStackCreator.getStack( + "§aMoonflower", + Material.BLUE_ORCHID, + 1, + "§7Upgrade your §aMoonflower §7tier to", + "§7increase your §6☘ Moonflower Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Moonflower Fortune: §6+15☘", + "", + "§eClick to view!" + )); + layout.slot(32, ItemStackCreator.getStack( + "§aWild Rose", + Material.ROSE_BUSH, + 1, + "§7Upgrade your §aWild Rose §7tier to", + "§7increase your §6☘ Wild Rose Fortune§7.", + "", + "§7Current Tier: §e5§7/§a9", + "§7Wild Rose Fortune: §6+25☘", + "", + "§eClick to view!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Upgrades" + )); + } +} diff --git a/garden_static_code_snippets/GUIDesk.java b/garden_static_code_snippets/GUIDesk.java new file mode 100644 index 0000000000..a3aa90f68b --- /dev/null +++ b/garden_static_code_snippets/GUIDesk.java @@ -0,0 +1,93 @@ +public class GUIDesk extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Desk", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(4, ItemStackCreator.getStack( + "§aGarden Level XIII", + Material.SUNFLOWER, + 1, + "§7Earn Garden experience by", + "§7accepting visitors' offers and", + "§7unlocking new milestones!", + "", + "§7Progress to Level XIV: §e71.2%", + "§2§l§m §f§l§m §e7,125§6/§e10k", + "", + "§7Level XIV Rewards:", + " §8+§b1 §eVisitor", + " §aTier VIII §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§7You currently have §2130 Crop Growth§7!", + "", + "§eClick to view!" + )); + layout.slot(19, ItemStackCreator.getStack( + "§aConfigure Plots", + Material.GRASS_BLOCK, + 1, + "§7Unlock access to new plots or modify", + "§7plots that you have already unlocked!", + "", + "§eClick to view!" + )); + layout.slot(21, ItemStackCreator.getStack( + "§aGarden Upgrades", + Material.GLISTERING_MELON_SLICE, + 1, + "§7Upgrade various aspects of your", + "§7garden to increase yield,", + "§7experience, and more.", + "", + "§eClick to view!" + )); + layout.slot(23, ItemStackCreator.getStack( + "§aSkyMart", + Material.EMERALD, + 1, + "§7Browse the wide variety of products", + "§7SkyMart has to offer. We are not", + "§7responsible for any injuries,", + "§7accidents, headaches, paper-cuts or", + "§7sudden outburst of tears. SkyMart", + "§7wishes you happy shopping.", + "", + "§eClick to view!" + )); + layout.slot(25, ItemStackCreator.getStack( + "§aGarden Milestones", + Material.GOLD_BLOCK, + 1, + "§7Achieve milestones on your Garden", + "§7to earn Garden XP and Farming XP.", + "", + "§eClick to view!" + )); + layout.slot(31, ItemStackCreator.getStack( + "§aGarden Skins", + Material.BEACON, + 1, + "§7View and select different skins for", + "§7your Garden!", + "", + "§eClick to view!" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aGarden Time", + Material.CLOCK, + 1, + "§7Modifies your Garden time.", + "", + "§cClick on a Day Saver in your", + "§cinventory to unlock! (0/2)" + )); + } +} diff --git a/garden_static_code_snippets/GUIGardenLevels.java b/garden_static_code_snippets/GUIGardenLevels.java new file mode 100644 index 0000000000..9f600506ed --- /dev/null +++ b/garden_static_code_snippets/GUIGardenLevels.java @@ -0,0 +1,237 @@ +public class GUIGardenLevels extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Levels", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(0, ItemStackCreator.getStack( + "§aGarden Levels", + Material.SUNFLOWER, + 1, + "§7Earn Garden experience by", + "§7accepting visitors' offers and", + "§7unlocking new milestones!", + "", + "§7Progress to Level XIV: §e71.2%", + "§2§l§m §f§l§m §e7,125§6/§e10k", + "", + "§8Increase your Garden Level to", + "§8unlock new visitors, crops and more!" + )); + layout.slot(2, ItemStackCreator.getStack( + "§aGarden Level VIII", + Material.LIME_STAINED_GLASS_PANE, + 8, + "§7Rewards:", + " §8+§b9 §eVisitors", + " §aCocoa Beans §7Crop", + " §6Moth Pest", + " §aTier IV §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(3, ItemStackCreator.getStack( + "§aGarden Level IX", + Material.LIME_STAINED_GLASS_PANE, + 9, + "§7Rewards:", + " §8+§b8 §eVisitors", + " §aMushroom §7Crop", + " §6Slug Pest", + " §aRed Barn Skin", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(4, ItemStackCreator.getStack( + "§aGarden Level X", + Material.DIAMOND_HOE, + 1, + "§7Rewards:", + " §8+§b9 §eVisitors", + " §aNether Wart §7Crop", + " §6Beetle Pest", + " §aTier V §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(9, ItemStackCreator.getStack( + "§aGarden Level I", + Material.LIME_STAINED_GLASS_PANE, + 1, + "§7Rewards:", + " §8+§b46 §eVisitors", + " §aWheat §7Crop", + "", + "§a§lUNLOCKED" + )); + layout.slot(11, ItemStackCreator.getStack( + "§aGarden Level VII", + Material.LIME_STAINED_GLASS_PANE, + 7, + "§7Rewards:", + " §8+§b10 §eVisitors", + " §aCactus §7Crop", + " §6Mite Pest", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aGarden Level XI", + Material.LIME_STAINED_GLASS_PANE, + 11, + "§7Rewards:", + " §8+§b3 §eVisitors", + " §aSunflower §7Crop", + " §aMoonflower §7Crop", + " §6Firefly Pest", + " §6Dragonfly Pest", + " §9Cabin Barn Skin", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(18, ItemStackCreator.getStack( + "§aGarden Level II", + Material.LIME_STAINED_GLASS_PANE, + 2, + "§7Rewards:", + " §8+§b9 §eVisitors", + " §aCarrot §7Crop", + " §aTier I §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(20, ItemStackCreator.getStack( + "§aGarden Level VI", + Material.LIME_STAINED_GLASS_PANE, + 6, + "§7Rewards:", + " §8+§b6 §eVisitors", + " §aMelon Slice §7Crop", + " §6Earthworm Pest", + " §aTier III §7Crop Upgrades", + " §aSunny Barn Skin", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(22, ItemStackCreator.getStack( + "§aGarden Level XII", + Material.LIME_STAINED_GLASS_PANE, + 12, + "§7Rewards:", + " §8+§b9 §eVisitors", + " §aWild Rose §7Crop", + " §6Praying Mantis Pest", + " §aTier VI §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(27, ItemStackCreator.getStack( + "§aGarden Level III", + Material.LIME_STAINED_GLASS_PANE, + 3, + "§7Rewards:", + " §8+§b13 §eVisitors", + " §aPotato §7Crop", + " §aMedieval Barn Skin", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(28, ItemStackCreator.getStack( + "§aGarden Level IV", + Material.LIME_STAINED_GLASS_PANE, + 4, + "§7Rewards:", + " §8+§b5 §eVisitors", + " §aPumpkin §7Crop", + " §aTier II §7Crop Upgrades", + " §8+§7Gearing Up Quest", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(29, ItemStackCreator.getStack( + "§aGarden Level V", + Material.DIAMOND_HOE, + 1, + "§7Rewards:", + " §8+§b5 §eVisitors", + " §aSugar Cane §7Crop", + " §6Fly Pest", + " §6Cricket Pest", + " §6Locust Pest", + " §6Rat Pest", + " §6Field Mouse Pest", + " §6Mosquito Pest", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(31, ItemStackCreator.getStack( + "§aGarden Level XIII", + Material.LIME_STAINED_GLASS_PANE, + 13, + "§7Rewards:", + " §8+§b2 §eVisitors", + " §aTier VII §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§a§lUNLOCKED" + )); + layout.slot(32, ItemStackCreator.getStack( + "§eGarden Level XIV", + Material.YELLOW_STAINED_GLASS_PANE, + 14, + "§7Rewards:", + " §8+§b1 §eVisitor", + " §aTier VIII §7Crop Upgrades", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth", + "", + "§7Progress to Level XIV: §e71.2%", + "§2§l§m §f§l§m §e7,125§6/§e10k" + )); + layout.slot(33, ItemStackCreator.getStack( + "§cGarden Level XV", + Material.DIAMOND_HOE, + 1, + "§7Rewards:", + " §8+§b2 §eVisitors", + " §aTier IX §7Crop Upgrades", + " §5Mansion Heights Barn Skin", + " §8+§b10 SkyBlock XP", + " §8+§210 Crop Growth" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUIGardenMilestones.java b/garden_static_code_snippets/GUIGardenMilestones.java new file mode 100644 index 0000000000..6e527d41c5 --- /dev/null +++ b/garden_static_code_snippets/GUIGardenMilestones.java @@ -0,0 +1,37 @@ +public class GUIGardenMilestones extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(21, ItemStackCreator.getStack( + "§aCrop Milestones", + Material.WHEAT, + 1, + "§7View progress and rewards for", + "§7crop milestones on your garden!", + "", + "§eClick to view!" + )); + layout.slot(23, ItemStackCreator.getStackHead( + "§aVisitor Milestones", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7View progress and rewards for", + "§7visitor milestones on your garden!", + "", + "§eClick to view!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUIGardenSkins.java b/garden_static_code_snippets/GUIGardenSkins.java new file mode 100644 index 0000000000..c823478eab --- /dev/null +++ b/garden_static_code_snippets/GUIGardenSkins.java @@ -0,0 +1,37 @@ +public class GUIGardenSkins extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Skins", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aBarn Skins", + Material.OAK_PLANKS, + 1, + "§7View and select different skins for", + "§7your Barn.", + "", + "§eClick to view!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§aGreenhouse Skins", + Material.WHITE_STAINED_GLASS, + 1, + "§7View and select different skins for", + "§7your Greenhouse.", + "", + "§eClick to view!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUIGardenUpgrades.java b/garden_static_code_snippets/GUIGardenUpgrades.java new file mode 100644 index 0000000000..d5297d02e2 --- /dev/null +++ b/garden_static_code_snippets/GUIGardenUpgrades.java @@ -0,0 +1,37 @@ +public class GUIGardenUpgrades extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Upgrades", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aCrop Upgrades", + Material.WHEAT, + 1, + "§7Increase your §6☘ Farming Fortune", + "§7for each crop by upgrading them!", + "", + "§eClick to view!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§aGreenhouse Upgrades", + Material.WHITE_STAINED_GLASS, + 1, + "§7Upgrade your Greenhouse to", + "§7improve its growth and yield.", + "", + "§eClick to view!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUIGreenhouseSkins.java b/garden_static_code_snippets/GUIGreenhouseSkins.java new file mode 100644 index 0000000000..a16e13af07 --- /dev/null +++ b/garden_static_code_snippets/GUIGreenhouseSkins.java @@ -0,0 +1,29 @@ +public class GUIGreenhouseSkins extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Greenhouse Skins", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(10, ItemStackCreator.getStack( + "§fDefault", + Material.WHITE_STAINED_GLASS, + 1, + "§7Select this skin for your Greenhouse!", + "", + "§f§lCOMMON COSMETIC", + "", + "§eClick to select!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Skins" + )); + } +} diff --git a/garden_static_code_snippets/GUIGreenhouseUpgrades.java b/garden_static_code_snippets/GUIGreenhouseUpgrades.java new file mode 100644 index 0000000000..88b07c09dc --- /dev/null +++ b/garden_static_code_snippets/GUIGreenhouseUpgrades.java @@ -0,0 +1,76 @@ +public class GUIGreenhouseUpgrades extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Greenhouse Upgrades", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aGrowth Speed", + Material.WHEAT_SEEDS, + 1, + "§7Upgrade your §aGrowth Speed §7tier to", + "§7increase your §bGreenhouse Growth", + "§bSpeed§7.", + "", + "§7Current Tier: §e4§7/§a9", + "§7Growth Speed: §b20%", + "", + "§eClick to view!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aPlant Yield", + Material.FLOWER_POT, + 1, + "§7Upgrade your §aPlant Yield §7tier to", + "§7increase your §eGreenhouse Plant", + "§eYield§7.", + "", + "§7Current Tier: §e2§7/§a9", + "§7Plant Yield: §e4%", + "", + "§eClick to view!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§cPlot Limit", + Material.GRAY_DYE, + 1, + "§7Unlock all Greenhouse crop slots to", + "§7use this!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Upgrades" + )); + layout.slot(32, ItemStackCreator.getStack( + "§6Crop Slots", + Material.COARSE_DIRT, + 1, + "§7The Greenhouse has a specific", + "§7amount of area for crops to grow in.", + "§7The grid below shows where crops", + "§7can be planted and grow.", + "", + "§7To unlock a slot, right-click it with an", + "§5Ethereal Vine §7in your hand.", + "", + "§7Your Slots:", + " §c██████████", + " §c██████████", + " §c██████████", + " §c████§a██§c████", + " §c███§a████§c███", + " §c███§a████§c███", + " §c████§a██§c████", + " §c██████████", + " §c██████████", + " §c██████████" + )); + } +} diff --git a/garden_static_code_snippets/GUIJacobSFarmingContests.java b/garden_static_code_snippets/GUIJacobSFarmingContests.java new file mode 100644 index 0000000000..bb5103b886 --- /dev/null +++ b/garden_static_code_snippets/GUIJacobSFarmingContests.java @@ -0,0 +1,74 @@ +public class GUIJacobSFarmingContests extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Jacob's Farming Contests", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aWhat is this?", + Material.OAK_SIGN, + 1, + "§8Instructions", + "", + "§7Every 3 SkyBlock days, I hold a contest", + "§7for 3 §efarming §7collections.", + "", + "§7The contests last 1 SkyBlock day.", + "§8(20 minutes).", + "", + "§7The §etop §7players who increase", + "§7their collection the most earn", + "§aJacob's Tickets §7and §dunique", + "§drewards§7!", + "", + "§bCo-ops do NOT pool their collection!", + "", + "§8You may participate in 1 collection", + "§8contest per event!", + "", + "§8Minions do not count in contests!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§6Upcoming Contests", + Material.CLOCK, + 1, + "§8Schedule", + "", + "§eEarly Summer 23rd", + "§e○ §7Cactus", + "§e○ §7Moonflower", + "§6☘ §7Wild Rose", + "", + "§eEarly Summer 26th", + "§e○ §7Pumpkin", + "§e○ §7Wheat", + "§6☘ §7Sugar Cane", + "", + "§8View this info in your full", + "§8SkyBlock calendar!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§bClaim your rewards!", + Material.WHEAT, + 1, + "§8Previous contests", + "", + "§7You've participated in §e11 §7previous", + "§7contests.", + "", + "§7Medals inventory:", + "§6§lGOLD §7medals: §60", + "§f§lSILVER §7medals: §f0", + "§c§lBRONZE §7medals: §c1", + "", + "§aYou have §f5 §aunclaimed awards!", + "", + "§eClick to view contests!" + )); + } +} diff --git a/garden_static_code_snippets/GUIManageChips.java b/garden_static_code_snippets/GUIManageChips.java new file mode 100644 index 0000000000..294207b258 --- /dev/null +++ b/garden_static_code_snippets/GUIManageChips.java @@ -0,0 +1,207 @@ +public class GUIManageChips extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Manage Chips", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(4, ItemStackCreator.getStackHead( + "§aManage Chips", + "2a4a77840449437d21f9d5f047f518413cb2f69e3ecbfb99386649c997ca1e91", + 1, + "§7Upgrade your §9Chips §7to gain powerful", + "§7buffs!", + "", + "§2✿ Sowdust", + "§7Sowdust is dropped from harvesting", + "§7crops in §aThe Garden §7and is used to", + "§7upgrade the Chips you've unlocked!", + "", + "§7Sowdust: §2198,605" + )); + layout.slot(20, ItemStackCreator.getStack( + "§cVermin Vaporizer Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Vermin Vaporizer ", + "§7Grants §2+3ൠ Bonus Pest Chance§7!", + "", + "§7Rarely dropped by the §2Dragonfly", + "§2Pest§7.", + "", + "§c§lLOCKED" + )); + layout.slot(21, ItemStackCreator.getStack( + "§cSynthesis Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Synthesis ", + "§7Analyzing a mutation in the §eCrop", + "§eAnalyzer §7gives §a1% §7more §cCopper§7!", + "", + "§7Rarely drops from the §aGreenhouse§7.", + "", + "§c§lLOCKED" + )); + layout.slot(22, ItemStackCreator.getStack( + "§cSowledge Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Sowledge ", + "§7Grants §3+1☯ Farming Wisdom§7!", + "", + "§7Unlocked from §aSkyMart§7.", + "", + "§c§lLOCKED" + )); + layout.slot(23, ItemStackCreator.getStack( + "§cMechamind Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Mechamind ", + "§7Grants §a1.5% §7more §eFarming Tool", + "§7Experience!", + "", + "§7Purchased from §bAnita's Shop§7.", + "", + "§c§lLOCKED" + )); + layout.slot(24, ItemStackCreator.getStack( + "§cHypercharge Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Hypercharge ", + "§7Temporary §6☘ Farming Fortune §7buffs", + "§7are §a3% §7stronger!", + "", + "§7Rarely gets rewarded from", + "§7completing §bVisitor Offers§7.", + "", + "§c§lLOCKED" + )); + layout.slot(29, ItemStackCreator.getStack( + "§cEvergreen Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Evergreen ", + "§7Gain §a2% §7more base crops when", + "§7harvesting in the §aGreenhouse§7!", + "", + "§7Rarely drops from the §aGreenhouse§7.", + "", + "§c§lLOCKED" + )); + layout.slot(30, ItemStackCreator.getStack( + "§cOverdrive Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Overdrive ", + "§7Grants §6+5☘ Crop Fortune §7for the", + "§7active crop during §eJacob's Farming", + "§eContest§7!", + "", + "§7Purchased from §bAnita's Shop§7.", + "", + "§c§lLOCKED" + )); + layout.slot(31, ItemStackCreator.getStackHead( + "§9Cropshot Chip", + "e85b6f92f03867f835d3179a42557b4c4bdd3545c9a8e9285bdd32dab464d08f", + 1, + "§7Level 2§8/10", + "", + "§6Ability: Cropshot ", + "§7Grants §6+6☘ Farming Fortune§7!", + "", + "§a§l=====[ UPGRADE ]=====", + "§6Ability: Cropshot ", + "§7Grants §6+9☘ Farming Fortune§7!", + "", + "§7Cost", + "§2200,000 Sowdust", + "", + "§7Redeem §a4 §7more §9Cropshot Chips §7to", + "§7upgrade this chip to §5§lEPIC§7!", + "", + "§9§lRARE GARDEN CHIP", + "", + "§eClick to level up!" + )); + layout.slot(32, ItemStackCreator.getStack( + "§cQuickdraw Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Quickdraw ", + "§7Decreases the time for §bVisitors §7to", + "§7appear when harvesting crops by", + "§a1.5%§7!", + "", + "§7Rarely gets rewarded from", + "§7completing §bVisitor Offers§7.", + "", + "§c§lLOCKED" + )); + layout.slot(33, ItemStackCreator.getStack( + "§cRarefinder Chip", + Material.GRAY_DYE, + 1, + "§7Level 0§8/10", + "", + "§6Ability: Rarefinder ", + "§7Increases the chance to find rare", + "§7items when breaking crops by §a2%§7!", + "", + "§7Rarely drops from farming §eCrops§7.", + "", + "§c§lLOCKED" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aChip Information", + Material.REDSTONE_TORCH, + 1, + "§7Chips can be upgraded using §2Sowdust§7.", + "§7To increase a Chip's §9maximum level§7,", + "§7you'll need to upgrade its rarity by", + "§ausing Chip items§7.", + "", + "§7• §9§lRARE §7Chips max at §aLevel 10", + "§7• §5§lEPIC §7Chips max at §aLevel 15", + "§7• §6§lLEGENDARY §7Chips max at §aLevel 20", + "", + "§7Increasing a Chip's rarity also", + "§7increases the scaling of its perks!" + )); + layout.slot(53, ItemStackCreator.getStackHead( + "§cReset Farming Chips", + "7c8489c03357d6d6abd9f4a3bd8824eb0f2841685ade95ff987ebe15b2e65fad", + 1, + "§7Resets all §9Chip §7levels back to 1 so", + "§7you can reallocate your §2Sowdust§7.", + "", + "§7You will be reimbursed with:", + " §8- §2100,000 Sowdust", + "", + "§eClick to reset!" + )); + } +} diff --git a/garden_static_code_snippets/GUIPesthunter.java b/garden_static_code_snippets/GUIPesthunter.java new file mode 100644 index 0000000000..ea85104682 --- /dev/null +++ b/garden_static_code_snippets/GUIPesthunter.java @@ -0,0 +1,71 @@ +public class GUIPesthunter extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Pesthunter", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aEmpty Vacuum Bag", + Material.HOPPER_MINECART, + 1, + "§7Empty your §6Vacuum Bag §7to receive", + "§7bonus §6☘ Farming Fortune §7while on", + "§7your §aGarden §7over the next §a30m§7!", + "", + "§7Vacuum Bag: §20 ൠ Pests", + "", + "§cYou don't have any Pests in your", + "§cVacuum Bag!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§ePesthunter Bonus", + Material.CLOCK, + 1, + "§7Exchange §2ൠ Pests §7from your", + "§6Vacuum Bag §7with the §6Pesthunter §7to", + "§7gain temporary §6☘ Farming Fortune", + "§7while on your §aGarden§7.", + "", + "§c§lINACTIVE" + )); + layout.slot(15, ItemStackCreator.getStack( + "§6Pesthunter's Wares", + Material.OAK_SIGN, + 1, + "§7Using §2ൠ Pests§7 as currency,", + "§7purchase a variety of bits 'n bobs", + "§7that §6Phillip§7 has held onto over the", + "§7years!", + "", + "§7Vacuum Bag: §20 ൠ Pests", + "", + "§eClick to view!" + )); + layout.slot(32, ItemStackCreator.getStack( + "§4§lൠ §cGarden Infestation", + Material.RED_DYE, + 1, + "§7Your §6☘ Farming Fortune §7decreases", + "§7as more §2ൠ Pests §7spawn in §aThe", + "§aGarden§7!", + "", + "§7For every §2+100ൠ Bonus Pest Chance", + "§7you have, you can spawn §a1 §7extra §2ൠ", + "§2Pest §7before you start losing fortune!", + "", + "§7Get rid of §2ൠ Pests §7to restore it!", + "", + " §8ൠ §70-3 Pests §8§lSAFE", + " §8ൠ §74 Pests §8-5% ☘", + " §8ൠ §75 Pests §8-15% ☘", + " §8ൠ §76 Pests §8-30% ☘", + " §8ൠ §77 Pests §8-50% ☘", + " §4ൠ §c8 Pests §4-75% ☘" + )); + } +} diff --git a/garden_static_code_snippets/GUIShifty.java b/garden_static_code_snippets/GUIShifty.java new file mode 100644 index 0000000000..edab26e152 --- /dev/null +++ b/garden_static_code_snippets/GUIShifty.java @@ -0,0 +1,45 @@ +public class GUIShifty extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Shifty", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(13, ItemStackCreator.getStackHead( + "§9Shifty", + "6c01b213574f630616dd00b7a3bc61774fd7d467136476bd7b7a99ec25ee2757", + 1, + "§9§lRARE", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a9" + )); + layout.slot(29, ItemStackCreator.getStack( + "§aAccept Offer", + Material.GREEN_TERRACOTTA, + 1, + "§7Items Required:", + " §9Enchanted Melon §8x8", + "", + "§7Rewards:", + " §8+§35.6k §7Farming XP", + " §8+§230 §7Garden Experience", + " §8+§c68 Copper", + "", + "§cMissing items to accept!" + )); + layout.slot(33, ItemStackCreator.getStack( + "§cRefuse Offer", + Material.RED_TERRACOTTA, + 1, + "§9Shifty §7will leave your §aGarden §7and", + "§7maybe come back later.", + "", + "§eClick to refuse!" + )); + } +} diff --git a/garden_static_code_snippets/GUISkyMart.java b/garden_static_code_snippets/GUISkyMart.java new file mode 100644 index 0000000000..f3a01f0ff2 --- /dev/null +++ b/garden_static_code_snippets/GUISkyMart.java @@ -0,0 +1,61 @@ +public class GUISkyMart extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("SkyMart", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + layout.slot(11, ItemStackCreator.getStack( + "§aFarming Essentials", + Material.GREEN_DYE, + 1, + "§7All the farming supplies you could", + "§7ever need!", + "", + "§eClick to view!" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aFarming Tools", + Material.DIAMOND_HOE, + 1, + "§7Purchase tools made specifically for", + "§7each crop!", + "", + "§eClick to view!" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aBarn Skins", + Material.OAK_PLANKS, + 1, + "§7Spruce up your Barn!", + "", + "§eClick to view!" + )); + layout.slot(14, ItemStackCreator.getStack( + "§aGreenhouse Skins", + Material.WHITE_STAINED_GLASS, + 1, + "§7Make your Greenhouse look fresh!", + "", + "§eClick to view!" + )); + layout.slot(15, ItemStackCreator.getStack( + "§aPests", + Material.CHEST_MINECART, + 1, + "§7Got pests? We got you.", + "", + "§eClick to view!" + )); + layout.slot(30, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Desk" + )); + } +} diff --git a/garden_static_code_snippets/GUIVisitorMilestones.java b/garden_static_code_snippets/GUIVisitorMilestones.java new file mode 100644 index 0000000000..53b4d3df80 --- /dev/null +++ b/garden_static_code_snippets/GUIVisitorMilestones.java @@ -0,0 +1,59 @@ +public class GUIVisitorMilestones extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Visitor Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(21, ItemStackCreator.getStackHead( + "§aUnique Visitors Served", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7Unlock unique visitors and complete", + "§7their offer on your island to", + "§7increase this milestone.", + "", + "§7Progress to Tier XII: §e0%", + "§f§l§m §e0§6/§e10", + "", + "§7Rewards:", + " §8+§2360 §7Garden Experience", + " §8+§310,000 §7Farming Experience", + " §8+§b3 SkyBlock XP" + )); + layout.slot(23, ItemStackCreator.getStackHead( + "§aOffers Accepted", + "c765fee97bcfae7c136b6a6b8ca95381af964d6aebc08bfdd2350af78e2cf51a", + 1, + "§7Accept offers from visitors to", + "§7increase this milestone.", + "", + "§7Progress to Tier XIV: §e24%", + "§2§l§m §f§l§m §e24§6/§e100", + "", + "§7Rewards:", + " §8+§2420 §7Garden Experience", + " §8+§330,000 §7Farming Experience", + " §8+§b3 SkyBlock XP" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Garden Milestones" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aVisitor's Logbook", + Material.BOOK, + 1, + "§7Browse and filter through all of the", + "§7visitors on the Garden.", + "", + "§eClick to browse!" + )); + } +} diff --git a/garden_static_code_snippets/GUIVisitorSLogbook.java b/garden_static_code_snippets/GUIVisitorSLogbook.java new file mode 100644 index 0000000000..939ead8ff4 --- /dev/null +++ b/garden_static_code_snippets/GUIVisitorSLogbook.java @@ -0,0 +1,368 @@ +public class GUIVisitorSLogbook extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Visitor's Logbook", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(4, ItemStackCreator.getStackHead( + "§aLogbook", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7Various NPCs will visit your island", + "§7and queue at your barn stand to", + "§7make offers.", + "", + "§7Harvesting crops will reduce the time", + "§7until the next visitor appears.", + "", + "§7Next Visitor: §c§lQueue Full!", + "", + "§7New Visitors come every §a10 minutes§7.", + "§7Upgrades in §c10 §7more unique visitors", + "§7served!" + )); + layout.slot(10, ItemStackCreator.getStack( + "§aRyu", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a18", + "§7Offers Accepted: §a15", + "§7Unlocked by: §bHieta" + )); + layout.slot(11, ItemStackCreator.getStack( + "§aLibrarian", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a17", + "§7Offers Accepted: §a15", + "§7Unlocked by: §bArikSquad" + )); + layout.slot(12, ItemStackCreator.getStack( + "§aOdawa", + Material.GRAY_DYE, + 1, + "§7Requirement:", + "§c✖ Talk to Odawa" + )); + layout.slot(13, ItemStackCreator.getStack( + "§aAndrew", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a15", + "§7Offers Accepted: §a12", + "§7Unlocked by: §bHieta" + )); + layout.slot(14, ItemStackCreator.getStack( + "§aDuke", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a14", + "§7Offers Accepted: §a11", + "§7Unlocked by: §bHieta" + )); + layout.slot(15, ItemStackCreator.getStackHead( + "§aJacob", + "7b8bb1b48f4babc67ce39547208fdbed722ca598cdf30681e367c6247cab1918", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a14", + "§7Offers Accepted: §a13", + "§7Unlocked by: §bHieta" + )); + layout.slot(16, ItemStackCreator.getStack( + "§aFriendly Hiker", + Material.GRAY_DYE, + 1, + "§7Requirement:", + "§c✖ Talk to Friendly Hiker" + )); + layout.slot(19, ItemStackCreator.getStackHead( + "§9Gwendolyn", + "5c34351606a99f62cab9e7c51283864753d2e67948b1f126baf70c12917233f0", + 1, + "§9§lRARE", + "", + "§7Times Visited: §a12", + "§7Offers Accepted: §a11", + "§7Unlocked by: §bHieta" + )); + layout.slot(20, ItemStackCreator.getStack( + "§aLeo", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a12", + "§7Offers Accepted: §a12", + "§7Unlocked by: §bHieta" + )); + layout.slot(21, ItemStackCreator.getStack( + "§aLynn", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a12", + "§7Offers Accepted: §a10", + "§7Unlocked by: §bHieta" + )); + layout.slot(22, ItemStackCreator.getStackHead( + "§aOringo", + "e49fe595c6a08adec8b9dab0986853271c6b87d897d7318b1badad2c34bd5a0e", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a12", + "§7Offers Accepted: §a11", + "§7Unlocked by: §bHieta" + )); + layout.slot(23, ItemStackCreator.getStackHead( + "§aWeaponsmith", + "af3f7a69c9a7ad95e90f212da304990c8be36e93197db874c2b0fe08106e323b", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a12", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(24, ItemStackCreator.getStack( + "§aArthur", + Material.GRAY_DYE, + 1, + "§7Requirement:", + "§c✖ Talk to Arthur" + )); + layout.slot(25, ItemStackCreator.getStackHead( + "§aEmissary Fraiser", + "9a43322511f4bba710bd78c98c90e83ac92a2da0f05a0a6951089bace0cf203a", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a11", + "§7Offers Accepted: §a11", + "§7Unlocked by: §bHieta" + )); + layout.slot(28, ItemStackCreator.getStack( + "§aFarmhand", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a11", + "§7Offers Accepted: §a8", + "§7Unlocked by: §bHieta" + )); + layout.slot(29, ItemStackCreator.getStackHead( + "§aTarwen", + "213cf0ca79a3611b8e05fe9e264fb2bf8d27e464dc12dc6e95dd0ae0c335a561", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a11", + "§7Offers Accepted: §a8", + "§7Unlocked by: §bArikSquad" + )); + layout.slot(30, ItemStackCreator.getStackHead( + "§aBanker Broadjaw", + "e59f8c4dfd1c583a8a54fa5cc7f8eb6c24d269af27c99fd9f2b2bbf40e70fed2", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a7", + "§7Unlocked by: §bArikSquad" + )); + layout.slot(31, ItemStackCreator.getStackHead( + "§aGuy", + "e9e23be7f04556fa33351a4c771a3f05f4a6d27de413a36d0230c61f71698799", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(32, ItemStackCreator.getStack( + "§aLiam", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(33, ItemStackCreator.getStackHead( + "§aRhys", + "6b20b23c1aa2be0270f016b4c90d6ee6b8330a17cfef87869d6ad60b2ffbf3b5", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a6", + "§7Unlocked by: §bArikSquad" + )); + layout.slot(34, ItemStackCreator.getStack( + "§9Shifty", + Material.GRAY_DYE, + 1, + "§7Requirement:", + "§c✖ Talk to Shifty" + )); + layout.slot(37, ItemStackCreator.getStackHead( + "§aTerry", + "9c3b72242f38b15c1312fac84e7cc73329236d20218e0ab78d08e2ac47806a48", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a10", + "§7Offers Accepted: §a8", + "§7Unlocked by: §bArikSquad" + )); + layout.slot(38, ItemStackCreator.getStackHead( + "§aEmissary Ceanna", + "e78c25ac8d793a73af31e5da3e8bf2d63a6c0026fde9a9682fc9caf615540c79", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a9", + "§7Offers Accepted: §a7", + "§7Unlocked by: §bHieta" + )); + layout.slot(39, ItemStackCreator.getStack( + "§aJack", + Material.VILLAGER_SPAWN_EGG, + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a9", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(40, ItemStackCreator.getStackHead( + "§aJotraeline Greatforge", + "85362b61f298dec2df765457d6b417e917d2309ebc8f691f0855489682ddec59", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a9", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(41, ItemStackCreator.getStackHead( + "§aShaggy", + "258f88dcbd2af110c77cffaa00eab7a499c00133e61575599d9e06e61b8a24a6", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a9", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(42, ItemStackCreator.getStackHead( + "§aTrevor", + "6102f82148461ced1f7b62e326eb2db3a94a33cba81d4281452af4d8aeca4991", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a9", + "§7Offers Accepted: §a9", + "§7Unlocked by: §bHieta" + )); + layout.slot(43, ItemStackCreator.getStackHead( + "§aAdventurer", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§a§lUNCOMMON", + "", + "§7Times Visited: §a8", + "§7Offers Accepted: §a7", + "§7Unlocked by: §bHieta" + )); + layout.slot(47, ItemStackCreator.getStack( + "§aUnlocked", + Material.EMERALD_BLOCK, + 1, + "", + "§8▶ Show All", + "§7 Unlocked", + "§7 Not Unlocked", + "", + "§bRight-click to go backwards!", + "§eClick to switch!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aSort", + Material.HOPPER, + 1, + "", + "§b▶ Visits", + "§7 Offers Accepted", + "§7 Rarity", + "§7 Alphabetically", + "", + "§bRight-click to go backwards!", + "§eClick to switch sort!" + )); + layout.slot(50, ItemStackCreator.getStack( + "§aRarity", + Material.ENDER_EYE, + 1, + "", + "§8▶ No filter", + "§7 Uncommon", + "§7 Rare", + "§7 Legendary", + "§7 Mythic", + "§7 Special", + "", + "§bRight-click to go backwards!", + "§eClick to switch rarity!" + )); + layout.slot(51, ItemStackCreator.getStack( + "§aVisited", + Material.WHITE_CARPET, + 1, + "", + "§8▶ Show All", + "§7 Visited", + "§7 Never Visited", + "", + "§bRight-click to go backwards!", + "§eClick to switch!" + )); + layout.slot(52, ItemStackCreator.getStack( + "§aOffers Accepted", + Material.GOLD_INGOT, + 1, + "", + "§8▶ Show All", + "§7 Completed At Least 1 Offer", + "§7 Never Completed An Offer", + "", + "§bRight-click to go backwards!", + "§eClick to switch!" + )); + layout.slot(53, ItemStackCreator.getStack( + "§aNext Page", + Material.ARROW, + 1, + "§ePage 2" + )); + } +} diff --git a/garden_static_code_snippets/GUIWheatUpgrades.java b/garden_static_code_snippets/GUIWheatUpgrades.java new file mode 100644 index 0000000000..f3fa1e9cd3 --- /dev/null +++ b/garden_static_code_snippets/GUIWheatUpgrades.java @@ -0,0 +1,133 @@ +public class GUIWheatUpgrades extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Wheat Upgrades", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + layout.slot(4, ItemStackCreator.getStack( + "§aWheat", + Material.WHEAT, + 1, + "§7Upgrade your §aWheat §7tier to increase", + "§7your §6☘ Wheat Fortune§7.", + "", + "§7Current Tier: §e3§7/§a9", + "§7Wheat Fortune: §6+15☘" + )); + layout.slot(18, ItemStackCreator.getStack( + "§aWheat I", + Material.LIME_STAINED_GLASS_PANE, + 1, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§a§lUNLOCKED" + )); + layout.slot(19, ItemStackCreator.getStack( + "§aWheat II", + Material.LIME_STAINED_GLASS_PANE, + 2, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§a§lUNLOCKED" + )); + layout.slot(20, ItemStackCreator.getStack( + "§aWheat III", + Material.LIME_STAINED_GLASS_PANE, + 3, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§a§lUNLOCKED" + )); + layout.slot(21, ItemStackCreator.getStack( + "§eWheat IV", + Material.YELLOW_STAINED_GLASS_PANE, + 4, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c50 Copper", + "", + "§eClick to unlock!" + )); + layout.slot(22, ItemStackCreator.getStack( + "§cWheat V", + Material.RED_STAINED_GLASS_PANE, + 5, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c100 Copper", + "", + "§cYou need to unlock the previous tier", + "§cfirst!" + )); + layout.slot(23, ItemStackCreator.getStack( + "§cWheat VI", + Material.RED_STAINED_GLASS_PANE, + 6, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c500 Copper", + "", + "§cYou need to unlock the previous tier", + "§cfirst!" + )); + layout.slot(24, ItemStackCreator.getStack( + "§cWheat VII", + Material.RED_STAINED_GLASS_PANE, + 7, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c1,000 Copper", + "", + "§cYou need to unlock the previous tier", + "§cfirst!" + )); + layout.slot(25, ItemStackCreator.getStack( + "§cWheat VIII", + Material.RED_STAINED_GLASS_PANE, + 8, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c2,000 Copper", + "", + "§cYou need to unlock the previous tier", + "§cfirst!" + )); + layout.slot(26, ItemStackCreator.getStack( + "§cWheat IX", + Material.RED_STAINED_GLASS_PANE, + 9, + "§6+5☘ Wheat Fortune", + "§8+§b1 SkyBlock XP", + "", + "§7Costs", + "§c4,000 Copper", + "", + "§cYou need to unlock the previous tier", + "§cfirst!" + )); + layout.slot(48, ItemStackCreator.getStack( + "§aGo Back", + Material.ARROW, + 1, + "§7To Crop Upgrades" + )); + } +} diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index f07e988672..333b7e9909 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(project(":type.jerrysworkshop")) implementation(project(":type.dungeonhub")) implementation(project(":type.skyblockgeneric")) + implementation(project(":type.garden")) implementation(project(":type.prototypelobby")) diff --git a/service.jacobscontest/build.gradle.kts b/service.jacobscontest/build.gradle.kts new file mode 100644 index 0000000000..56f314df77 --- /dev/null +++ b/service.jacobscontest/build.gradle.kts @@ -0,0 +1,47 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + java + application + id("com.gradleup.shadow") version "9.3.1" +} + +group = "net.swofty" +version = "3.0" + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } +} + +repositories { + maven("https://jitpack.io") + mavenCentral() +} + +dependencies { + implementation(project(":service.generic")) + implementation(project(":commons")) + implementation("com.github.ben-manes.caffeine:caffeine:3.2.3") + implementation("org.tinylog:tinylog-api:2.7.0") + implementation("org.tinylog:tinylog-impl:2.7.0") + + implementation("net.minestom:minestom:2025.12.20c-1.21.11") { + exclude(group = "org.jboss.shrinkwrap.resolver", module = "shrinkwrap-resolver-depchain") + } +} + +application { + mainClass.set("net.swofty.service.jacobscontest.JacobsContestService") +} + +tasks { + named("shadowJar") { + archiveBaseName.set("ServiceJacobsContest") + archiveClassifier.set("") + archiveVersion.set("") + } +} diff --git a/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestConfig.java b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestConfig.java new file mode 100644 index 0000000000..b4274e0ccf --- /dev/null +++ b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestConfig.java @@ -0,0 +1,107 @@ +package net.swofty.service.jacobscontest; + +import net.swofty.commons.YamlFileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public final class JacobsContestConfig { + private static final Path CONFIG_PATH = Path.of("./configuration/skyblock/garden/jacobs_contests.yml"); + + private final Map root; + + private JacobsContestConfig(Map root) { + this.root = root; + } + + public static JacobsContestConfig load() { + File file = CONFIG_PATH.toFile(); + if (!file.exists()) { + return new JacobsContestConfig(Map.of()); + } + try { + Map loaded = YamlFileUtils.loadYaml(file); + return new JacobsContestConfig(loaded == null ? Map.of() : loaded); + } catch (IOException e) { + return new JacobsContestConfig(Map.of()); + } + } + + public long getYearLength() { + return getLong(getSection(root, "calendar"), "year_length", 8_928_000L); + } + + public long getDayLength() { + return getLong(getSection(root, "calendar"), "day_length", 24_000L); + } + + public int getIntervalDays() { + return getInt(getSection(root, "calendar"), "contest_interval_days", 3); + } + + public int getDurationDays() { + return getInt(getSection(root, "calendar"), "contest_duration_days", 1); + } + + public List getCrops() { + Object value = root.get("crops"); + if (value instanceof List list) { + return list.stream().map(String::valueOf).toList(); + } + return List.of( + "WHEAT", + "CARROT", + "POTATO", + "PUMPKIN", + "MELON_SLICE", + "MUSHROOM", + "CACTUS", + "SUGAR_CANE", + "NETHER_WART", + "COCOA_BEANS", + "SUNFLOWER", + "MOONFLOWER", + "WILD_ROSE" + ); + } + + @SuppressWarnings("unchecked") + private static Map getSection(Map root, String key) { + Object value = root.get(key); + if (value instanceof Map map) { + return (Map) map; + } + return Map.of(); + } + + private static int getInt(Map root, String key, int def) { + Object value = root.get(key); + if (value instanceof Number number) { + return number.intValue(); + } + if (value != null) { + try { + return Integer.parseInt(String.valueOf(value)); + } catch (NumberFormatException ignored) { + } + } + return def; + } + + private static long getLong(Map root, String key, long def) { + Object value = root.get(key); + if (value instanceof Number number) { + return number.longValue(); + } + if (value != null) { + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException ignored) { + } + } + return def; + } +} diff --git a/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestScheduler.java b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestScheduler.java new file mode 100644 index 0000000000..242f9f0640 --- /dev/null +++ b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestScheduler.java @@ -0,0 +1,87 @@ +package net.swofty.service.jacobscontest; + +import net.swofty.commons.protocol.objects.jacobscontest.GetJacobContestScheduleProtocol; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Random; + +public final class JacobsContestScheduler { + private static final JacobsContestConfig CONFIG = JacobsContestConfig.load(); + + private JacobsContestScheduler() { + } + + public static List generateYear(int year) { + List result = new ArrayList<>(); + int contestIndex = 0; + long yearLength = CONFIG.getYearLength(); + long contestInterval = (long) CONFIG.getIntervalDays() * CONFIG.getDayLength(); + long contestDuration = (long) CONFIG.getDurationDays() * CONFIG.getDayLength(); + for (long start = 0; start < yearLength; start += contestInterval) { + result.add(new GetJacobContestScheduleProtocol.ContestScheduleEntry( + contestIndex, + start, + start + contestDuration, + (int) (start / CONFIG.getDayLength()) + 1, + pickCrops(year, contestIndex) + )); + contestIndex++; + } + return result; + } + + public static int getYear(long calendarElapsed) { + return (int) (calendarElapsed / CONFIG.getYearLength()) + 1; + } + + public static int getActiveIndex(long calendarElapsed, List entries) { + long elapsedInYear = calendarElapsed % CONFIG.getYearLength(); + for (GetJacobContestScheduleProtocol.ContestScheduleEntry entry : entries) { + if (elapsedInYear >= entry.startTime() && elapsedInYear < entry.endTime()) { + return entry.index(); + } + } + return -1; + } + + public static List getUpcoming( + long calendarElapsed, + int requestedCount + ) { + int year = getYear(calendarElapsed); + long elapsedInYear = calendarElapsed % CONFIG.getYearLength(); + List yearSchedule = generateYear(year); + List result = new ArrayList<>(); + + for (GetJacobContestScheduleProtocol.ContestScheduleEntry entry : yearSchedule) { + if (entry.endTime() <= elapsedInYear) { + continue; + } + result.add(entry); + if (result.size() >= requestedCount) { + return result; + } + } + + List nextYear = generateYear(year + 1); + for (GetJacobContestScheduleProtocol.ContestScheduleEntry entry : nextYear) { + result.add(entry); + if (result.size() >= requestedCount) { + break; + } + } + return result; + } + + private static List pickCrops(int year, int contestIndex) { + Random random = new Random(Objects.hash(year, contestIndex, "jacobs-contest")); + List pool = new ArrayList<>(CONFIG.getCrops()); + List picks = new ArrayList<>(3); + for (int i = 0; i < 3 && !pool.isEmpty(); i++) { + picks.add(pool.remove(random.nextInt(pool.size()))); + } + return picks; + } +} diff --git a/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestService.java b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestService.java new file mode 100644 index 0000000000..128c9a7c0f --- /dev/null +++ b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/JacobsContestService.java @@ -0,0 +1,23 @@ +package net.swofty.service.jacobscontest; + +import net.swofty.commons.ServiceType; +import net.swofty.commons.redis.RedisMessageHandler; +import net.swofty.service.generic.SkyBlockService; + +import java.util.List; + +public class JacobsContestService implements SkyBlockService { + static void main(String[] args) { + SkyBlockService.init(new JacobsContestService()); + } + + @Override + public ServiceType getType() { + return ServiceType.JACOBS_CONTEST; + } + + @Override + public List getEndpoints() { + return loopThroughPackage("net.swofty.service.jacobscontest.endpoints", RedisMessageHandler.class).toList(); + } +} diff --git a/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/endpoints/EndpointGetJacobContestSchedule.java b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/endpoints/EndpointGetJacobContestSchedule.java new file mode 100644 index 0000000000..16c7ef6f55 --- /dev/null +++ b/service.jacobscontest/src/main/java/net/swofty/service/jacobscontest/endpoints/EndpointGetJacobContestSchedule.java @@ -0,0 +1,35 @@ +package net.swofty.service.jacobscontest.endpoints; + +import net.swofty.commons.protocol.RedisProtocol; +import net.swofty.commons.protocol.objects.jacobscontest.GetJacobContestScheduleProtocol; +import net.swofty.commons.redis.RedisMessageContext; +import net.swofty.commons.redis.RedisMessageHandler; +import net.swofty.service.jacobscontest.JacobsContestScheduler; + +import java.util.List; + +public class EndpointGetJacobContestSchedule implements RedisMessageHandler< + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage, + GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse> { + + @Override + public RedisProtocol< + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage, + GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse> protocol() { + return new GetJacobContestScheduleProtocol(); + } + + @Override + public GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse handle( + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage body, + RedisMessageContext context + ) { + int year = JacobsContestScheduler.getYear(body.calendarElapsed()); + List schedule = JacobsContestScheduler.generateYear(year); + return new GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse( + year, + JacobsContestScheduler.getActiveIndex(body.calendarElapsed(), schedule), + JacobsContestScheduler.getUpcoming(body.calendarElapsed(), Math.max(1, body.upcomingCount())) + ); + } +} diff --git a/service.punishment/build.gradle.kts b/service.punishment/build.gradle.kts index 2cae97e8d8..0855a1899a 100644 --- a/service.punishment/build.gradle.kts +++ b/service.punishment/build.gradle.kts @@ -32,7 +32,6 @@ dependencies { implementation(libs.mongodb.bson) implementation(libs.mongodb.driver.sync) - //implementation(libs.atlas.redis) implementation(libs.jedis) } diff --git a/settings.gradle.kts b/settings.gradle.kts index bdf08bca84..0857d57f1a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include(":type.galatea") include(":type.backwaterbayou") include(":type.jerrysworkshop") include(":type.island") +include(":type.garden") include(":type.hub") include(":type.dungeonhub") include(":type.bedwarslobby") @@ -53,6 +54,7 @@ include(":service.datamutex") include(":service.party") include(":service.orchestrator") include(":service.darkauction") +include(":service.jacobscontest") include(":service.friend") include(":service.replay") include(":service.punishment") diff --git a/type.deepcaverns/src/main/java/net/swofty/type/deepcaverns/npcs/NPCRhys.java b/type.deepcaverns/src/main/java/net/swofty/type/deepcaverns/npcs/NPCRhys.java index 5b4393a61c..b9dd88b6c7 100644 --- a/type.deepcaverns/src/main/java/net/swofty/type/deepcaverns/npcs/NPCRhys.java +++ b/type.deepcaverns/src/main/java/net/swofty/type/deepcaverns/npcs/NPCRhys.java @@ -43,4 +43,4 @@ public void onClick(NPCInteractEvent event) { SkyBlockPlayer player = (SkyBlockPlayer) event.player(); player.notImplemented(); } -} \ No newline at end of file +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCBankerBroadjaw.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCBankerBroadjaw.java index 4f69cfcfc9..7c9ad8b83f 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCBankerBroadjaw.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCBankerBroadjaw.java @@ -3,11 +3,10 @@ import net.minestom.server.coordinate.Pos; import net.swofty.type.generic.entity.npc.HypixelNPC; import net.swofty.type.generic.entity.npc.configuration.HumanConfiguration; +import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.skyblockgeneric.gui.inventories.banker.GUIBanker; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - public class NPCBankerBroadjaw extends HypixelNPC { public NPCBankerBroadjaw() { @@ -43,4 +42,4 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIBanker().open(event.player()); } -} \ No newline at end of file +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCFragilis.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCFragilis.java index c93ec2fdfe..796f98f4a1 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCFragilis.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCFragilis.java @@ -4,9 +4,8 @@ import net.swofty.type.dwarvenmines.gui.fragilis.GUIHandyBlockGuide; import net.swofty.type.generic.entity.npc.HypixelNPC; import net.swofty.type.generic.entity.npc.configuration.HumanConfiguration; -import net.swofty.type.generic.user.HypixelPlayer; - import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.generic.user.HypixelPlayer; public class NPCFragilis extends HypixelNPC { @@ -43,4 +42,4 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIHandyBlockGuide().open(event.player()); } -} \ No newline at end of file +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCPuzzler.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCPuzzler.java index 85134d3e4c..1dae729c3d 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCPuzzler.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCPuzzler.java @@ -43,5 +43,4 @@ public void onClick(NPCInteractEvent event) { SkyBlockPlayer player = (SkyBlockPlayer) event.player(); player.notImplemented(); } - -} \ No newline at end of file +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCRhys.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCRhys.java index 56b8ff8110..dc65728bb3 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCRhys.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCRhys.java @@ -4,9 +4,8 @@ import net.swofty.type.dwarvenmines.gui.GUIGuide; import net.swofty.type.generic.entity.npc.HypixelNPC; import net.swofty.type.generic.entity.npc.configuration.HumanConfiguration; -import net.swofty.type.generic.user.HypixelPlayer; - import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.generic.user.HypixelPlayer; public class NPCRhys extends HypixelNPC { @@ -43,4 +42,4 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIGuide().open(event.player()); } -} \ No newline at end of file +} diff --git a/type.garden/build.gradle.kts b/type.garden/build.gradle.kts new file mode 100644 index 0000000000..f754cc25d1 --- /dev/null +++ b/type.garden/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + java +} + +group = "net.swofty" +version = "3.0" + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) + } +} + +dependencies { + implementation(project(":type.skyblockgeneric")) + implementation(project(":type.generic")) + implementation(project(":commons")) + implementation(project(":proxy.api")) + compileOnly("net.minestom:minestom:2025.12.20c-1.21.11") { + exclude(group = "org.jboss.shrinkwrap.resolver", module = "shrinkwrap-resolver-depchain") + } + implementation("org.tinylog:tinylog-api:2.7.0") + implementation("org.tinylog:tinylog-impl:2.7.0") + implementation("org.mongodb:bson:4.11.2") + implementation("dev.hollowcube:polar:1.15.0") + implementation("it.unimi.dsi:fastutil:8.5.18") + implementation("dev.hollowcube:schem:2.0.1") +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/GardenCropRegistry.java b/type.garden/src/main/java/net/swofty/type/garden/GardenCropRegistry.java new file mode 100644 index 0000000000..87f5e54d8e --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/GardenCropRegistry.java @@ -0,0 +1,57 @@ +package net.swofty.type.garden; + +import net.minestom.server.item.Material; +import net.swofty.commons.skyblock.statistics.ItemStatistic; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +public final class GardenCropRegistry { + private static final List VISITOR_CROP_POOL = List.of( + "WHEAT", + "CARROT", + "POTATO", + "PUMPKIN", + "SUGAR_CANE", + "MELON_SLICE", + "CACTUS", + "COCOA_BEANS", + "MUSHROOM", + "NETHER_WART", + "SUNFLOWER", + "MOONFLOWER", + "WILD_ROSE" + ); + + private static final Map CROP_BY_MATERIAL = Map.ofEntries( + Map.entry(Material.WHEAT, new CropContext("WHEAT", ItemStatistic.WHEAT_FORTUNE, false)), + Map.entry(Material.CARROT, new CropContext("CARROT", ItemStatistic.CARROT_FORTUNE, false)), + Map.entry(Material.POTATO, new CropContext("POTATO", ItemStatistic.POTATO_FORTUNE, false)), + Map.entry(Material.PUMPKIN, new CropContext("PUMPKIN", ItemStatistic.PUMPKIN_FORTUNE, false)), + Map.entry(Material.CARVED_PUMPKIN, new CropContext("PUMPKIN", ItemStatistic.PUMPKIN_FORTUNE, false)), + Map.entry(Material.MELON, new CropContext("MELON_SLICE", ItemStatistic.MELON_FORTUNE, false)), + Map.entry(Material.SUGAR_CANE, new CropContext("SUGAR_CANE", ItemStatistic.SUGAR_CANE_FORTUNE, true)), + Map.entry(Material.CACTUS, new CropContext("CACTUS", ItemStatistic.CACTUS_FORTUNE, true)), + Map.entry(Material.BROWN_MUSHROOM, new CropContext("MUSHROOM", ItemStatistic.MUSHROOM_FORTUNE, false)), + Map.entry(Material.RED_MUSHROOM, new CropContext("MUSHROOM", ItemStatistic.MUSHROOM_FORTUNE, false)), + Map.entry(Material.NETHER_WART, new CropContext("NETHER_WART", ItemStatistic.NETHER_WART_FORTUNE, false)), + Map.entry(Material.SUNFLOWER, new CropContext("SUNFLOWER", null, false)), + Map.entry(Material.BLUE_ORCHID, new CropContext("MOONFLOWER", null, false)), + Map.entry(Material.ROSE_BUSH, new CropContext("WILD_ROSE", null, false)) + ); + + private GardenCropRegistry() { + } + + public static CropContext fromMaterial(Material material) { + return CROP_BY_MATERIAL.get(material); + } + + public static List getVisitorCropPool() { + return VISITOR_CROP_POOL; + } + + public record CropContext(String cropId, @Nullable ItemStatistic specificFortune, boolean doubleBreakCrop) { + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/GardenServices.java b/type.garden/src/main/java/net/swofty/type/garden/GardenServices.java new file mode 100644 index 0000000000..8c3fd734b5 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/GardenServices.java @@ -0,0 +1,69 @@ +package net.swofty.type.garden; + +import net.swofty.type.garden.chips.GardenChipService; +import net.swofty.type.garden.composter.GardenComposterService; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.greenhouse.GardenGreenhouseService; +import net.swofty.type.garden.level.GardenLevelService; +import net.swofty.type.garden.milestone.GardenMilestoneService; +import net.swofty.type.garden.pest.GardenPestService; +import net.swofty.type.garden.shop.GardenDeskService; +import net.swofty.type.garden.visitor.GardenVisitorService; + +public final class GardenServices { + private static final GardenVisitorService VISITOR_SERVICE = new GardenVisitorService(); + private static final GardenPestService PEST_SERVICE = new GardenPestService(); + private static final GardenComposterService COMPOSTER_SERVICE = new GardenComposterService(); + private static final GardenChipService CHIP_SERVICE = new GardenChipService(); + private static final GardenGreenhouseService GREENHOUSE_SERVICE = new GardenGreenhouseService(); + private static final GardenLevelService LEVEL_SERVICE = new GardenLevelService(); + private static final GardenDeskService DESK_SERVICE = new GardenDeskService(); + private static final GardenMilestoneService MILESTONE_SERVICE = new GardenMilestoneService(); + + private GardenServices() { + } + + public static void initialize() { + GardenConfigRegistry.reload(); + VISITOR_SERVICE.reload(); + PEST_SERVICE.reload(); + COMPOSTER_SERVICE.reload(); + CHIP_SERVICE.reload(); + GREENHOUSE_SERVICE.reload(); + LEVEL_SERVICE.reload(); + DESK_SERVICE.reload(); + MILESTONE_SERVICE.reload(); + } + + public static GardenVisitorService visitors() { + return VISITOR_SERVICE; + } + + public static GardenPestService pests() { + return PEST_SERVICE; + } + + public static GardenComposterService composter() { + return COMPOSTER_SERVICE; + } + + public static GardenChipService chips() { + return CHIP_SERVICE; + } + + public static GardenGreenhouseService greenhouse() { + return GREENHOUSE_SERVICE; + } + + public static GardenLevelService levels() { + return LEVEL_SERVICE; + } + + public static GardenDeskService desk() { + return DESK_SERVICE; + } + + public static GardenMilestoneService milestones() { + return MILESTONE_SERVICE; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/TypeGardenLoader.java b/type.garden/src/main/java/net/swofty/type/garden/TypeGardenLoader.java new file mode 100644 index 0000000000..43f57d208d --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/TypeGardenLoader.java @@ -0,0 +1,126 @@ +package net.swofty.type.garden; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Pos; +import net.swofty.commons.CustomWorlds; +import net.swofty.commons.ServerType; +import net.swofty.commons.ServiceType; +import net.swofty.commons.redis.RedisMessageHandler; +import net.swofty.type.garden.commands.GardenAdminCommand; +import net.swofty.type.garden.npc.NPCAnita; +import net.swofty.type.garden.npc.NPCGardenVisitorSlot; +import net.swofty.type.garden.npc.NPCJacob; +import net.swofty.type.garden.npc.NPCJeff; +import net.swofty.type.garden.npc.NPCPamela; +import net.swofty.type.garden.npc.NPCPesthunterPhillip; +import net.swofty.type.garden.npc.NPCSam; +import net.swofty.type.garden.pest.GardenPestRuntime; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.garden.visitor.GardenBarnRuntime; +import net.swofty.type.garden.visitor.GardenVisitorRuntime; +import net.swofty.type.generic.SkyBlockTypeLoader; +import net.swofty.type.generic.entity.npc.HypixelNPC; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.tab.TablistManager; +import net.swofty.type.generic.tab.TablistModule; +import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; +import net.swofty.type.skyblockgeneric.tabmodules.AccountInformationModule; +import org.jetbrains.annotations.Nullable; +import org.tinylog.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class TypeGardenLoader implements SkyBlockTypeLoader { + private final List npcs = List.of( + new NPCSam(), + new NPCAnita(), + new NPCPamela(), + new NPCJacob(), + new NPCJeff(), + new NPCPesthunterPhillip(), + new NPCGardenVisitorSlot(1), + new NPCGardenVisitorSlot(2), + new NPCGardenVisitorSlot(3), + new NPCGardenVisitorSlot(4), + new NPCGardenVisitorSlot(5) + ); + + @Override + public ServerType getType() { + return ServerType.SKYBLOCK_GARDEN; + } + + @Override + public void onInitialize(MinecraftServer server) { + Logger.info("TypeGardenLoader initialized!"); + GardenServices.initialize(); + MinecraftServer.getCommandManager().register(new GardenAdminCommand().getCommand()); + npcs.forEach(HypixelNPC::register); + GardenPestRuntime.start(MinecraftServer.getSchedulerManager()); + GardenVisitorRuntime.start(MinecraftServer.getSchedulerManager()); + GardenBarnRuntime.start(MinecraftServer.getSchedulerManager()); + SkyBlockGarden.runVacantLoop(MinecraftServer.getSchedulerManager()); + } + + @Override + public void afterInitialize(MinecraftServer server) { + } + + @Override + public LoaderValues getLoaderValues() { + return new LoaderValues((_) -> new Pos(-5.5, 71, 17.5, 180, 0), true); + } + + @Override + public List getRequiredServices() { + return List.of( + ServiceType.AUCTION_HOUSE, + ServiceType.ITEM_TRACKER, + ServiceType.DATA_MUTEX, + ServiceType.JACOBS_CONTEST + ); + } + + @Override + public TablistManager getTablistManager() { + return new TablistManager() { + @Override + public List getModules() { + return new ArrayList<>(List.of(new AccountInformationModule())); + } + }; + } + + @Override + public List getTraditionalEvents() { + return SkyBlockGenericLoader.loopThroughPackage( + "net.swofty.type.garden.events.traditional", + HypixelEventClass.class + ).collect(Collectors.toList()); + } + + @Override + public List getCustomEvents() { + return SkyBlockGenericLoader.loopThroughPackage( + "net.swofty.type.garden.events.custom", + HypixelEventClass.class + ).collect(Collectors.toList()); + } + + @Override + public List getNPCs() { + return npcs; + } + + @Override + public List> getProxyHandlers() { + return List.of(); + } + + @Override + public @Nullable CustomWorlds getMainInstance() { + return null; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/chips/GardenChipService.java b/type.garden/src/main/java/net/swofty/type/garden/chips/GardenChipService.java new file mode 100644 index 0000000000..656de9c70a --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/chips/GardenChipService.java @@ -0,0 +1,84 @@ +package net.swofty.type.garden.chips; + +import net.swofty.type.garden.config.GardenConfigRegistry; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GardenChipService { + private Map config = Map.of(); + private List> chips = List.of(); + private final Map levelCosts = new HashMap<>(); + private final Map greenhouseMultipliers = new HashMap<>(); + + public void reload() { + config = GardenConfigRegistry.getConfig("chips.yml"); + chips = GardenConfigRegistry.getMapList(config, "chips"); + + levelCosts.clear(); + for (Map level : GardenConfigRegistry.getMapList(config, "level_costs")) { + levelCosts.put( + GardenConfigRegistry.getInt(level, "level", 1), + GardenConfigRegistry.getLong(level, "cost", 0) + ); + } + + greenhouseMultipliers.clear(); + GardenConfigRegistry.getSection(config, "greenhouse_crop_multipliers") + .forEach((key, value) -> greenhouseMultipliers.put(key, Double.parseDouble(String.valueOf(value)))); + } + + public String resolveRarity(int consumed) { + if (consumed >= 16) { + return "LEGENDARY"; + } + if (consumed >= 4) { + return "EPIC"; + } + if (consumed >= 1) { + return "RARE"; + } + return "LOCKED"; + } + + public double calculateGardenSowdust(double totalFortune, boolean doubleBreakCrop) { + double multiplier = doubleBreakCrop ? 2D : 1D; + return Math.ceil(totalFortune / 100D) / multiplier; + } + + public double calculateGreenhouseSowdust(Map baseCropAmounts, double totalFortune) { + double total = 0D; + double fortuneMultiplier = 1D + (totalFortune / 100D); + for (Map.Entry entry : baseCropAmounts.entrySet()) { + double cropMultiplier = greenhouseMultipliers.getOrDefault(entry.getKey(), 1D); + long normalized = Math.round(entry.getValue() / (5D * cropMultiplier)); + total += 5D * normalized * fortuneMultiplier; + } + return total; + } + + public long getLevelCost(int level) { + return levelCosts.getOrDefault(level, 0L); + } + + public List> getChips() { + return chips; + } + + public Map getChip(String id) { + return chips.stream() + .filter(entry -> GardenConfigRegistry.getString(entry, "id", "").equalsIgnoreCase(id)) + .findFirst() + .orElse(Map.of()); + } + + public int getMaxLevel(String rarity) { + return switch (rarity == null ? "LOCKED" : rarity.toUpperCase()) { + case "RARE" -> 10; + case "EPIC" -> 15; + case "LEGENDARY" -> 20; + default -> 0; + }; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/commands/GardenAdminCommand.java b/type.garden/src/main/java/net/swofty/type/garden/commands/GardenAdminCommand.java new file mode 100644 index 0000000000..ff89ad9d51 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/commands/GardenAdminCommand.java @@ -0,0 +1,365 @@ +package net.swofty.type.garden.commands; + +import net.minestom.server.command.builder.arguments.ArgumentGroup; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.command.builder.arguments.number.ArgumentDouble; +import net.minestom.server.command.builder.arguments.number.ArgumentInteger; +import net.minestom.server.command.builder.arguments.number.ArgumentLong; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.garden.visitor.GardenBarnRuntime; +import net.swofty.type.garden.visitor.GardenVisitorRuntime; +import net.swofty.type.generic.command.CommandParameters; +import net.swofty.type.generic.command.HypixelCommand; +import net.swofty.type.generic.user.categories.Rank; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSupport; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.Locale; + +@CommandParameters( + labels = "gardenadmin", + description = "Mutates Garden data for testing", + usage = "/gardenadmin
...", + permission = Rank.STAFF, + allowsConsole = false +) +public class GardenAdminCommand extends HypixelCommand { + @Override + public void registerUsage(MinestomCommand command) { + ArgumentInteger intAmount = ArgumentType.Integer("amount"); + ArgumentLong longAmount = ArgumentType.Long("amount"); + ArgumentDouble doubleAmount = ArgumentType.Double("amount"); + + ArgumentGroup coreLevel = ArgumentType.Group("core_level", ArgumentType.Literal("core"), ArgumentType.Literal("level"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.core(player).setLevel(Math.max(1, context.get(coreLevel).get("amount"))); + sender.sendMessage("§aGarden level set."); + }, coreLevel); + + ArgumentGroup coreXp = ArgumentType.Group("core_xp", ArgumentType.Literal("core"), ArgumentType.Literal("xp"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.core(player).setExperience(Math.max(0L, context.get(coreXp).get("amount"))); + sender.sendMessage("§aGarden XP set."); + }, coreXp); + + ArgumentGroup coreCopper = ArgumentType.Group("core_copper", ArgumentType.Literal("core"), ArgumentType.Literal("copper"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.core(player).setCopper(Math.max(0L, context.get(coreCopper).get("amount"))); + sender.sendMessage("§aCopper set."); + }, coreCopper); + + ArgumentGroup coreBarnSkinUnlock = ArgumentType.Group("core_barnskin_unlock", + ArgumentType.Literal("core"), ArgumentType.Literal("barnskin"), ArgumentType.Literal("unlock"), ArgumentType.String("skin_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String skinId = normalize(context.get(coreBarnSkinUnlock).get("skin_id")); + GardenGuiSupport.core(player).getOwnedBarnSkins().add(skinId); + GardenBarnRuntime.requestImmediateSync(player); + sender.sendMessage("§aUnlocked barn skin §e" + skinId + "§a."); + }, coreBarnSkinUnlock); + + ArgumentGroup coreBarnSkinSelect = ArgumentType.Group("core_barnskin_select", + ArgumentType.Literal("core"), ArgumentType.Literal("barnskin"), ArgumentType.Literal("select"), ArgumentType.String("skin_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String skinId = normalize(context.get(coreBarnSkinSelect).get("skin_id")); + GardenGuiSupport.core(player).setSelectedBarnSkin(skinId); + GardenBarnRuntime.requestImmediateSync(player); + sender.sendMessage("§aSelected barn skin §e" + skinId + "§a."); + }, coreBarnSkinSelect); + + ArgumentGroup coreTime = ArgumentType.Group("core_time", + ArgumentType.Literal("core"), ArgumentType.Literal("time"), ArgumentType.Word("mode")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mode = normalize(context.get(coreTime).get("mode")); + if (!mode.equals("DYNAMIC") && !mode.equals("DAY") && !mode.equals("NIGHT")) { + sender.sendMessage("§cTime mode must be DYNAMIC, DAY, or NIGHT."); + return; + } + GardenGuiSupport.core(player).setSelectedTimeMode(mode); + sender.sendMessage("§aGarden time mode set to §e" + mode + "§a."); + }, coreTime); + + ArgumentGroup visitorsSpawn = ArgumentType.Group("visitors_spawn", + ArgumentType.Literal("visitors"), ArgumentType.Literal("spawn"), ArgumentType.String("visitor_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String visitorId = normalize(context.get(visitorsSpawn).get("visitor_id")); + GardenData.GardenVisitorState state = GardenVisitorRuntime.createVisitorState(player, visitorId); + if (state == null) { + sender.sendMessage("§cUnknown visitor §e" + visitorId + "§c."); + return; + } + GardenGuiSupport.visitors(player).getActiveVisitors().add(state); + GardenBarnRuntime.requestImmediateSync(player); + sender.sendMessage("§aSpawned visitor §e" + visitorId + "§a."); + }, visitorsSpawn); + + ArgumentGroup visitorsQueue = ArgumentType.Group("visitors_queue", + ArgumentType.Literal("visitors"), ArgumentType.Literal("queue"), ArgumentType.String("visitor_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String visitorId = normalize(context.get(visitorsQueue).get("visitor_id")); + GardenData.GardenVisitorState state = GardenVisitorRuntime.createVisitorState(player, visitorId); + if (state == null) { + sender.sendMessage("§cUnknown visitor §e" + visitorId + "§c."); + return; + } + state.setQueued(true); + GardenGuiSupport.visitors(player).getQueuedVisitors().add(state); + GardenBarnRuntime.requestImmediateSync(player); + sender.sendMessage("§aQueued visitor §e" + visitorId + "§a."); + }, visitorsQueue); + + ArgumentGroup visitorsClear = ArgumentType.Group("visitors_clear", ArgumentType.Literal("visitors"), ArgumentType.Literal("clear")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.visitors(player).getActiveVisitors().clear(); + GardenGuiSupport.visitors(player).getQueuedVisitors().clear(); + GardenBarnRuntime.requestImmediateSync(player); + sender.sendMessage("§aCleared active and queued visitors."); + }, visitorsClear); + + ArgumentGroup visitorsAcceptCount = ArgumentType.Group("visitors_acceptcount", + ArgumentType.Literal("visitors"), ArgumentType.Literal("acceptcount"), ArgumentType.String("visitor_id"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String visitorId = normalize(context.get(visitorsAcceptCount).get("visitor_id")); + GardenGuiSupport.visitors(player).getServedCounts().put(visitorId, Math.max(0, context.get(visitorsAcceptCount).get("amount"))); + sender.sendMessage("§aUpdated accepted offer count for §e" + visitorId + "§a."); + }, visitorsAcceptCount); + + ArgumentGroup visitorsVisitCount = ArgumentType.Group("visitors_visitcount", + ArgumentType.Literal("visitors"), ArgumentType.Literal("visitcount"), ArgumentType.String("visitor_id"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String visitorId = normalize(context.get(visitorsVisitCount).get("visitor_id")); + GardenGuiSupport.visitors(player).getVisitCounts().put(visitorId, Math.max(0, context.get(visitorsVisitCount).get("amount"))); + sender.sendMessage("§aUpdated visit count for §e" + visitorId + "§a."); + }, visitorsVisitCount); + + ArgumentGroup visitorsServedUnique = ArgumentType.Group("visitors_servedunique", + ArgumentType.Literal("visitors"), ArgumentType.Literal("servedunique"), ArgumentType.Word("mode"), ArgumentType.String("visitor_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mode = normalize(context.get(visitorsServedUnique).get("mode")); + String visitorId = normalize(context.get(visitorsServedUnique).get("visitor_id")); + if (mode.equals("ADD")) { + GardenGuiSupport.core(player).getServedUniqueVisitors().add(visitorId); + } else if (mode.equals("REMOVE")) { + GardenGuiSupport.core(player).getServedUniqueVisitors().remove(visitorId); + } else { + sender.sendMessage("§cMode must be add or remove."); + return; + } + sender.sendMessage("§aUpdated served-unique set for §e" + visitorId + "§a."); + }, visitorsServedUnique); + + ArgumentGroup visitorsNextArrival = ArgumentType.Group("visitors_nextarrival", + ArgumentType.Literal("visitors"), ArgumentType.Literal("nextarrival"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + long seconds = Math.max(0L, context.get(visitorsNextArrival).get("amount")); + GardenGuiSupport.visitors(player).setNextArrivalAt(System.currentTimeMillis() + (seconds * 1000L)); + sender.sendMessage("§aNext arrival timer updated."); + }, visitorsNextArrival); + + ArgumentGroup visitorsFlag = ArgumentType.Group("visitors_flag", + ArgumentType.Literal("visitors"), ArgumentType.Literal("flag"), ArgumentType.String("key"), ArgumentType.Boolean("enabled")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String key = normalize(context.get(visitorsFlag).get("key")); + boolean enabled = context.get(visitorsFlag).get("enabled"); + if (enabled) { + GardenGuiSupport.personal(player).getVisitorRequirementFlags().add(key); + } else { + GardenGuiSupport.personal(player).getVisitorRequirementFlags().remove(key); + } + sender.sendMessage("§aVisitor flag §e" + key + " §aupdated."); + }, visitorsFlag); + + ArgumentGroup visitorsCounter = ArgumentType.Group("visitors_counter", + ArgumentType.Literal("visitors"), ArgumentType.Literal("counter"), ArgumentType.String("key"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String key = normalize(context.get(visitorsCounter).get("key")); + long value = Math.max(0L, context.get(visitorsCounter).get("amount")); + GardenGuiSupport.personal(player).getVisitorRequirementCounters().put(key, value); + sender.sendMessage("§aVisitor counter §e" + key + " §aset to §e" + value + "§a."); + }, visitorsCounter); + + ArgumentGroup personalTickets = ArgumentType.Group("personal_tickets", + ArgumentType.Literal("personal"), ArgumentType.Literal("jacobstickets"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.personal(player).setJacobsTickets(Math.max(0, context.get(personalTickets).get("amount"))); + sender.sendMessage("§aJacob's Tickets updated."); + }, personalTickets); + + ArgumentGroup personalMedal = ArgumentType.Group("personal_medal", + ArgumentType.Literal("personal"), ArgumentType.Literal("medal"), ArgumentType.Word("medal_type"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String medalType = normalize(context.get(personalMedal).get("medal_type")); + GardenGuiSupport.personal(player).getMedals().put(medalType, Math.max(0, context.get(personalMedal).get("amount"))); + sender.sendMessage("§aUpdated medal count for §e" + medalType + "§a."); + }, personalMedal); + + ArgumentGroup personalSpoken = ArgumentType.Group("personal_spoken", + ArgumentType.Literal("personal"), ArgumentType.Literal("spoken"), ArgumentType.Word("mode"), ArgumentType.String("npc_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mode = normalize(context.get(personalSpoken).get("mode")); + String npcId = GardenProgressionSupport.normalizeSpokenKey(context.get(personalSpoken).get("npc_id")); + if (mode.equals("ADD")) { + GardenGuiSupport.personal(player).getSpokenNpcFlags().add(npcId); + } else if (mode.equals("REMOVE")) { + GardenGuiSupport.personal(player).getSpokenNpcFlags().remove(npcId); + } else { + sender.sendMessage("§cMode must be add or remove."); + return; + } + sender.sendMessage("§aUpdated spoken NPC flag for §e" + npcId + "§a."); + }, personalSpoken); + + ArgumentGroup personalDonate = ArgumentType.Group("personal_donate", + ArgumentType.Literal("personal"), ArgumentType.Literal("donated"), ArgumentType.Word("mode"), ArgumentType.String("item_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mode = normalize(context.get(personalDonate).get("mode")); + String itemId = GardenProgressionSupport.normalizeKey(context.get(personalDonate).get("item_id")); + if (mode.equals("ADD")) { + GardenGuiSupport.personal(player).getDonatedItems().add(itemId); + } else if (mode.equals("REMOVE")) { + GardenGuiSupport.personal(player).getDonatedItems().remove(itemId); + } else { + sender.sendMessage("§cMode must be add or remove."); + return; + } + sender.sendMessage("§aUpdated donated item flag for §e" + itemId + "§a."); + }, personalDonate); + + ArgumentGroup personalExport = ArgumentType.Group("personal_export", + ArgumentType.Literal("personal"), ArgumentType.Literal("exported"), ArgumentType.String("item_id"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String itemId = GardenProgressionSupport.normalizeKey(context.get(personalExport).get("item_id")); + long amount = Math.max(0L, context.get(personalExport).get("amount")); + GardenGuiSupport.personal(player).getExportedItems().put(itemId, amount); + sender.sendMessage("§aUpdated exported amount for §e" + itemId + " §ato §e" + amount + "§a."); + }, personalExport); + + ArgumentGroup personalAnitaUnlock = ArgumentType.Group("personal_anitaunlock", + ArgumentType.Literal("personal"), ArgumentType.Literal("anitaunlock"), ArgumentType.Word("mode"), ArgumentType.String("unlock_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mode = normalize(context.get(personalAnitaUnlock).get("mode")); + String unlockId = normalize(context.get(personalAnitaUnlock).get("unlock_id")); + if (mode.equals("ADD")) { + GardenGuiSupport.personal(player).getAnitaPurchases().add(unlockId); + } else if (mode.equals("REMOVE")) { + GardenGuiSupport.personal(player).getAnitaPurchases().remove(unlockId); + } else { + sender.sendMessage("§cMode must be add or remove."); + return; + } + sender.sendMessage("§aUpdated Anita unlock §e" + unlockId + "§a."); + }, personalAnitaUnlock); + + ArgumentGroup greenhouseUnlock = ArgumentType.Group("greenhouse_unlock", + ArgumentType.Literal("greenhouse"), ArgumentType.Literal("unlock"), ArgumentType.Boolean("enabled")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.greenhouse(player).setBlueprintUnlocked(context.get(greenhouseUnlock).get("enabled")); + sender.sendMessage("§aGreenhouse unlock updated."); + }, greenhouseUnlock); + + ArgumentGroup greenhouseDna = ArgumentType.Group("greenhouse_dna", + ArgumentType.Literal("greenhouse"), ArgumentType.Literal("dna"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.greenhouse(player).setDnaMilestone(Math.max(0, context.get(greenhouseDna).get("amount"))); + sender.sendMessage("§aGreenhouse DNA milestone updated."); + }, greenhouseDna); + + ArgumentGroup greenhouseMutationHarvest = ArgumentType.Group("greenhouse_mutationharvest", + ArgumentType.Literal("greenhouse"), ArgumentType.Literal("mutationharvest"), ArgumentType.String("mutation_id"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String mutationId = normalize(context.get(greenhouseMutationHarvest).get("mutation_id")); + GardenGuiSupport.greenhouse(player).getMutationHarvests().put(mutationId, Math.max(0, context.get(greenhouseMutationHarvest).get("amount"))); + sender.sendMessage("§aUpdated greenhouse mutation harvest for §e" + mutationId + "§a."); + }, greenhouseMutationHarvest); + + ArgumentGroup pestsSpawn = ArgumentType.Group("pests_spawn", + ArgumentType.Literal("pests"), ArgumentType.Literal("spawn"), ArgumentType.String("pest_id")); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + String pestId = normalize(context.get(pestsSpawn).get("pest_id")); + boolean knownPest = GardenServices.pests().getPests().stream() + .anyMatch(entry -> normalize(GardenConfigRegistry.getString(entry, "id", "")).equals(pestId)); + if (!knownPest) { + sender.sendMessage("§cUnknown pest §e" + pestId + "§c."); + return; + } + GardenData.GardenPestState pest = new GardenData.GardenPestState(); + pest.setPestId(pestId); + pest.setPlotId("central"); + pest.setSpawnedAt(System.currentTimeMillis()); + GardenGuiSupport.pests(player).getActivePests().add(pest); + sender.sendMessage("§aSpawned pest §e" + pestId + "§a."); + }, pestsSpawn); + + ArgumentGroup pestsStored = ArgumentType.Group("pests_stored", + ArgumentType.Literal("pests"), ArgumentType.Literal("stored"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.pests(player).setStoredPests(Math.max(0, context.get(pestsStored).get("amount"))); + sender.sendMessage("§aStored pests count updated."); + }, pestsStored); + + ArgumentGroup pestsRepellent = ArgumentType.Group("pests_repellent", + ArgumentType.Literal("pests"), ArgumentType.Literal("repellent"), longAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + long seconds = Math.max(0L, context.get(pestsRepellent).get("amount")); + GardenGuiSupport.pests(player).setRepellentEndsAt(System.currentTimeMillis() + (seconds * 1000L)); + sender.sendMessage("§aRepellent timer updated."); + }, pestsRepellent); + + ArgumentGroup composterMatter = ArgumentType.Group("composter_matter", + ArgumentType.Literal("composter"), ArgumentType.Literal("matter"), doubleAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.composter(player).setOrganicMatter(Math.max(0D, context.get(composterMatter).get("amount"))); + sender.sendMessage("§aOrganic matter updated."); + }, composterMatter); + + ArgumentGroup composterFuel = ArgumentType.Group("composter_fuel", + ArgumentType.Literal("composter"), ArgumentType.Literal("fuel"), doubleAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.composter(player).setFuel(Math.max(0D, context.get(composterFuel).get("amount"))); + sender.sendMessage("§aFuel updated."); + }, composterFuel); + + ArgumentGroup composterCompost = ArgumentType.Group("composter_compost", + ArgumentType.Literal("composter"), ArgumentType.Literal("compost"), intAmount); + command.addSyntax((sender, context) -> { + SkyBlockPlayer player = (SkyBlockPlayer) sender; + GardenGuiSupport.composter(player).setCompostAvailable(Math.max(0, context.get(composterCompost).get("amount"))); + sender.sendMessage("§aCompost available updated."); + }, composterCompost); + } + + private static String normalize(String value) { + return value == null ? "" : value.trim().replace(' ', '_').toUpperCase(Locale.ROOT); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/composter/GardenComposterService.java b/type.garden/src/main/java/net/swofty/type/garden/composter/GardenComposterService.java new file mode 100644 index 0000000000..4ae903a66f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/composter/GardenComposterService.java @@ -0,0 +1,104 @@ +package net.swofty.type.garden.composter; + +import net.swofty.type.garden.config.GardenConfigRegistry; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GardenComposterService { + private Map config = Map.of(); + private final Map> branches = new HashMap<>(); + private final Map organicMatterValues = new HashMap<>(); + private final Map fuelValues = new HashMap<>(); + private List> upgradeTiers = List.of(); + + public void reload() { + config = GardenConfigRegistry.getConfig("composter.yml"); + branches.clear(); + GardenConfigRegistry.getSection(config, "branches").forEach((key, value) -> { + if (value instanceof Map map) { + @SuppressWarnings("unchecked") + Map typed = (Map) map; + branches.put(key, typed); + } + }); + organicMatterValues.clear(); + GardenConfigRegistry.getSection(config, "organic_matter_values") + .forEach((key, value) -> organicMatterValues.put(key, Double.parseDouble(String.valueOf(value)))); + + fuelValues.clear(); + GardenConfigRegistry.getSection(config, "fuel_values") + .forEach((key, value) -> fuelValues.put(key, Double.parseDouble(String.valueOf(value)))); + + upgradeTiers = GardenConfigRegistry.getMapList(config, "upgrade_tiers"); + } + + public long getBaseProductionSeconds() { + return GardenConfigRegistry.getLong(config, "base_production_seconds", 600); + } + + public int getBaseOrganicMatterCost() { + return GardenConfigRegistry.getInt(config, "base_organic_matter_cost", 4000); + } + + public int getBaseFuelCost() { + return GardenConfigRegistry.getInt(config, "base_fuel_cost", 2000); + } + + public double getOrganicMatterValue(String itemId) { + return organicMatterValues.getOrDefault(itemId, 0D); + } + + public double getFuelValue(String itemId) { + return fuelValues.getOrDefault(itemId, 0D); + } + + public Map getOrganicMatterValues() { + return Map.copyOf(organicMatterValues); + } + + public Map getFuelValues() { + return Map.copyOf(fuelValues); + } + + public double calculateOrganicMatterCost(int costReductionTier) { + return getBaseOrganicMatterCost() * Math.max(0D, 1D - (costReductionTier * 0.01D)); + } + + public double calculateFuelCost(int costReductionTier) { + return getBaseFuelCost() * Math.max(0D, 1D - (costReductionTier * 0.01D)); + } + + public double calculateSpeedMultiplier(int speedTier) { + return 1D + (speedTier * 0.20D); + } + + public double calculateMultiDropChance(int multiDropTier) { + return multiDropTier * 0.03D; + } + + public List> getUpgradeTiers() { + return upgradeTiers; + } + + public int getBranchUnlockGardenLevel(String branch) { + return GardenConfigRegistry.getInt(branches.getOrDefault(branch, Map.of()), "unlock_garden_level", 0); + } + + public int getFuelCapacity(int tier) { + Map branch = branches.getOrDefault("fuel_cap", Map.of()); + int base = GardenConfigRegistry.getInt(branch, "base_capacity", 100000); + int perTier = GardenConfigRegistry.getInt(branch, "per_tier_capacity", 30000); + int max = GardenConfigRegistry.getInt(branch, "max_capacity", base); + return Math.min(max, base + (Math.max(0, tier) * perTier)); + } + + public int getOrganicMatterCapacity(int tier) { + Map branch = branches.getOrDefault("organic_matter_cap", Map.of()); + int base = GardenConfigRegistry.getInt(branch, "base_capacity", 40000); + int perTier = GardenConfigRegistry.getInt(branch, "per_tier_capacity", 30000); + int max = GardenConfigRegistry.getInt(branch, "max_capacity", base); + return Math.min(max, base + (Math.max(0, tier) * perTier)); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/config/GardenBarnSkinDefinition.java b/type.garden/src/main/java/net/swofty/type/garden/config/GardenBarnSkinDefinition.java new file mode 100644 index 0000000000..89a30e33de --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/config/GardenBarnSkinDefinition.java @@ -0,0 +1,13 @@ +package net.swofty.type.garden.config; + +public record GardenBarnSkinDefinition( + String id, + String displayName, + String rarity, + String schematicFile, + String unlockSource, + int offsetX, + int offsetY, + int offsetZ +) { +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/config/GardenConfigRegistry.java b/type.garden/src/main/java/net/swofty/type/garden/config/GardenConfigRegistry.java new file mode 100644 index 0000000000..6b25ffdd43 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/config/GardenConfigRegistry.java @@ -0,0 +1,151 @@ +package net.swofty.type.garden.config; + +import net.swofty.commons.YamlFileUtils; +import org.tinylog.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class GardenConfigRegistry { + public static final Path CONFIG_DIR = Path.of("./configuration/skyblock/garden"); + private static final List CONFIG_FILES = List.of( + "levels.yml", + "plots.yml", + "barn_skins.yml", + "crop_upgrades.yml", + "composter.yml", + "copper_shop.yml", + "visitors.yml", + "visitor_dialogue.yml", + "pests.yml", + "pest_drops.yml", + "chips.yml", + "greenhouse.yml", + "mutations.yml", + "npc_anchors.yml", + "jacobs_contests.yml", + "milestones.yml" + ); + + private static final Map> CONFIGS = new HashMap<>(); + + private GardenConfigRegistry() { + } + + public static synchronized void reload() { + CONFIGS.clear(); + File directory = CONFIG_DIR.toFile(); + YamlFileUtils.ensureDirectoryExists(directory); + + for (String fileName : CONFIG_FILES) { + File file = CONFIG_DIR.resolve(fileName).toFile(); + if (!file.exists()) { + CONFIGS.put(fileName, Collections.emptyMap()); + continue; + } + + try { + Map loaded = YamlFileUtils.loadYaml(file); + CONFIGS.put(fileName, loaded == null ? Collections.emptyMap() : loaded); + } catch (IOException e) { + Logger.error(e, "Failed to load Garden config {}", fileName); + CONFIGS.put(fileName, Collections.emptyMap()); + } + } + } + + public static Map getConfig(String fileName) { + return CONFIGS.getOrDefault(fileName, Collections.emptyMap()); + } + + @SuppressWarnings("unchecked") + public static Map getSection(Map root, String key) { + Object value = root.get(key); + if (value instanceof Map map) { + return (Map) map; + } + return Collections.emptyMap(); + } + + @SuppressWarnings("unchecked") + public static List> getMapList(Map root, String key) { + Object value = root.get(key); + if (value instanceof List list) { + return (List>) list; + } + return List.of(); + } + + @SuppressWarnings("unchecked") + public static List getList(Map root, String key) { + Object value = root.get(key); + if (value instanceof List list) { + return (List) list; + } + return List.of(); + } + + public static String getString(Map root, String key, String def) { + Object value = root.get(key); + return value == null ? def : String.valueOf(value); + } + + public static int getInt(Map root, String key, int def) { + Object value = root.get(key); + if (value instanceof Number number) { + return number.intValue(); + } + if (value != null) { + try { + return Integer.parseInt(String.valueOf(value)); + } catch (NumberFormatException ignored) { + } + } + return def; + } + + public static long getLong(Map root, String key, long def) { + Object value = root.get(key); + if (value instanceof Number number) { + return number.longValue(); + } + if (value != null) { + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException ignored) { + } + } + return def; + } + + public static double getDouble(Map root, String key, double def) { + Object value = root.get(key); + if (value instanceof Number number) { + return number.doubleValue(); + } + if (value != null) { + try { + return Double.parseDouble(String.valueOf(value)); + } catch (NumberFormatException ignored) { + return 0.0d; // kinda dumb this hides problems + } + } + return def; + } + + public static boolean getBoolean(Map root, String key, boolean def) { + Object value = root.get(key); + if (value instanceof Boolean bool) { + return bool; + } + if (value != null) { + return Boolean.parseBoolean(String.valueOf(value)); + } + return def; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/config/GardenPlotDefinition.java b/type.garden/src/main/java/net/swofty/type/garden/config/GardenPlotDefinition.java new file mode 100644 index 0000000000..968ba87862 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/config/GardenPlotDefinition.java @@ -0,0 +1,11 @@ +package net.swofty.type.garden.config; + +public record GardenPlotDefinition( + String id, + String displayName, + String group, + int gardenLevel, + boolean defaultUnlocked, + GardenRegion region +) { +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/config/GardenRegion.java b/type.garden/src/main/java/net/swofty/type/garden/config/GardenRegion.java new file mode 100644 index 0000000000..882a3be550 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/config/GardenRegion.java @@ -0,0 +1,15 @@ +package net.swofty.type.garden.config; + +import net.minestom.server.coordinate.Point; + +public record GardenRegion(int minX, int maxX, int minZ, int maxZ) { + public boolean contains(Point point) { + int x = point.blockX(); + int z = point.blockZ(); + return x >= minX && x <= maxX && z >= minZ && z <= maxZ; + } + + public int width() { + return (maxX - minX) + 1; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/events/custom/ActionGardenBlockBreak.java b/type.garden/src/main/java/net/swofty/type/garden/events/custom/ActionGardenBlockBreak.java new file mode 100644 index 0000000000..971cc56f56 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/custom/ActionGardenBlockBreak.java @@ -0,0 +1,123 @@ +package net.swofty.type.garden.events.custom; + +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.commons.skyblock.statistics.ItemStatistic; +import net.swofty.type.garden.GardenCropRegistry; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.garden.plot.GardenPlotService; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.event.phase.PhasedEvent; +import net.swofty.type.skyblockgeneric.event.custom.CustomBlockBreakEvent; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.item.SkyBlockItem; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class ActionGardenBlockBreak implements HypixelEventClass { + @PhasedEvent(node = EventNodes.CUSTOM, requireDataLoaded = true) + public void run(CustomBlockBreakEvent event) { + SkyBlockPlayer player = event.getPlayer(); + if (!player.isOnGarden() || Boolean.TRUE.equals(event.getPlayerPlaced())) { + return; + } + + GardenCropRegistry.CropContext crop = GardenCropRegistry.fromMaterial(event.getMaterial()); + if (crop == null) { + return; + } + + long now = System.currentTimeMillis(); + GardenData.GardenVisitorsData visitors = GardenGuiSupport.visitors(player); + GardenData.GardenPersonalData personal = GardenGuiSupport.personal(player); + visitors.setLastFarmingActivityAt(now); + + double totalFortune = player.getStatistics().allStatistics().getOverall(ItemStatistic.FARMING_FORTUNE) + + (crop.specificFortune() == null + ? 0D + : player.getStatistics().allStatistics().getOverall(crop.specificFortune())); + personal.setSowdust(personal.getSowdust() + GardenServices.chips().calculateGardenSowdust(totalFortune, crop.doubleBreakCrop())); + + long harvestedAmount = getHarvestedAmount(event, crop.cropId()); + GardenServices.milestones().advanceCropMilestone(player, crop.cropId(), harvestedAmount); + trySpawnPest(player, crop, now); + } + + private long getHarvestedAmount(CustomBlockBreakEvent event, String cropId) { + return event.getDrops().stream() + .filter(drop -> { + ItemType type = drop.getAttributeHandler().getPotentialType(); + if (type == null) { + return false; + } + if ("MUSHROOM".equalsIgnoreCase(cropId)) { + return type.name().contains("MUSHROOM"); + } + return type.name().equalsIgnoreCase(cropId); + }) + .mapToLong(SkyBlockItem::getAmount) + .sum(); + } + + private void trySpawnPest(SkyBlockPlayer player, GardenCropRegistry.CropContext crop, long now) { + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + GardenData.GardenPestsData pests = GardenGuiSupport.pests(player); + if (core.getLevel() < GardenConfigRegistry.getInt( + GardenConfigRegistry.getConfig("pests.yml"), + "start_garden_level", + 5 + )) { + return; + } + if (now < pests.getCooldownEndsAt()) { + return; + } + + List> eligible = GardenServices.pests().getPests().stream() + .filter(entry -> !GardenConfigRegistry.getBoolean(entry, "trap_only", false)) + .filter(entry -> core.getLevel() >= GardenConfigRegistry.getInt(entry, "garden_level", 5)) + .filter(entry -> GardenConfigRegistry.getString(entry, "crop", "").equalsIgnoreCase(crop.cropId())) + .toList(); + if (eligible.isEmpty()) { + return; + } + if (ThreadLocalRandom.current().nextDouble() > GardenServices.pests().getBaseSpawnChance()) { + return; + } + + Map chosen = eligible.get(ThreadLocalRandom.current().nextInt(eligible.size())); + SkyBlockGarden garden = GardenGuiSupport.garden(player); + if (garden == null) { + return; + } + + GardenPlotService plotService = garden.getPlotService(); + String plotId = plotService.getPlotAt(player.getPosition()) == null + ? "central" + : plotService.getPlotAt(player.getPosition()).id(); + + GardenData.GardenPestState pest = new GardenData.GardenPestState(); + pest.setPestId(GardenConfigRegistry.getString(chosen, "id", "")); + pest.setPlotId(plotId); + pest.setSpawnedAt(now); + pests.getActivePests().add(pest); + pests.setCooldownEndsAt(now + GardenServices.pests().calculateSpawnCooldownSeconds(new net.swofty.type.garden.pest.GardenPestService.CooldownModifiers( + false, + 0, + false, + 0, + pests.getRepellentEndsAt() > now, + pests.isRepellentMax() + )) * 1000L); + pests.setLastSpawnCheckAt(now); + + String displayName = GardenConfigRegistry.getString(chosen, "display_name", "Pest"); + player.sendMessage("§cA §6" + displayName + " §chas infested your Garden!"); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionGardenSave.java b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionGardenSave.java new file mode 100644 index 0000000000..200e2b0e5c --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionGardenSave.java @@ -0,0 +1,20 @@ +package net.swofty.type.garden.events.traditional; + +import lombok.SneakyThrows; +import net.minestom.server.event.player.PlayerDisconnectEvent; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.event.phase.PhasedEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class ActionGardenSave implements HypixelEventClass { + @SneakyThrows + @PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = false) + public void run(PlayerDisconnectEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); + if (player.getSkyBlockGarden() instanceof SkyBlockGarden garden) { + garden.runVacantCheck(); + } + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerJoin.java b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerJoin.java new file mode 100644 index 0000000000..fd7de9daf9 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerJoin.java @@ -0,0 +1,21 @@ +package net.swofty.type.garden.events.traditional; + +import lombok.SneakyThrows; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.event.phase.EventPhase; +import net.swofty.type.generic.event.phase.PhasedEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class ActionPlayerJoin implements HypixelEventClass { + @SneakyThrows + @PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = false, phase = EventPhase.CONNECT) + public void run(AsyncPlayerConfigurationEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); + event.setSpawningInstance(HypixelConst.getEmptyInstance()); + player.setRespawnPoint(new Pos(0, 100, 0)); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerTeleport.java b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerTeleport.java new file mode 100644 index 0000000000..36fa1895af --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerTeleport.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.events.traditional; + +import net.minestom.server.event.player.PlayerSpawnEvent; +import net.minestom.server.instance.SharedInstance; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.event.EventNodes; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.event.phase.EventPhase; +import net.swofty.type.generic.event.phase.PhasedEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.UUID; + +public class ActionPlayerTeleport implements HypixelEventClass { + @PhasedEvent(node = EventNodes.PLAYER, requireDataLoaded = false, phase = EventPhase.POST_SPAWN) + public void run(PlayerSpawnEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); + if (!event.isFirstSpawn()) { + return; + } + UUID profileId = player.getSkyblockDataHandler().getCurrentProfileId(); + SkyBlockGarden garden = SkyBlockGarden.getGarden(profileId); + if (garden == null) { + garden = new SkyBlockGarden(profileId); + } + player.setSkyBlockGarden(garden); + + SharedInstance instance = garden.getSharedInstance().join(); + player.setInstance(instance, player.getRespawnPoint()); + player.teleport(player.getRespawnPoint()); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/greenhouse/GardenGreenhouseService.java b/type.garden/src/main/java/net/swofty/type/garden/greenhouse/GardenGreenhouseService.java new file mode 100644 index 0000000000..a49c2b5d36 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/greenhouse/GardenGreenhouseService.java @@ -0,0 +1,77 @@ +package net.swofty.type.garden.greenhouse; + +import net.swofty.type.garden.config.GardenConfigRegistry; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GardenGreenhouseService { + private Map greenhouse = Map.of(); + private Map mutations = Map.of(); + private final Map mutationVineChances = new HashMap<>(); + private final Map greenhouseUnlockCosts = new HashMap<>(); + private List> crops = List.of(); + private List> harvestBounty = List.of(); + private List> dnaMilestones = List.of(); + + public void reload() { + greenhouse = GardenConfigRegistry.getConfig("greenhouse.yml"); + mutations = GardenConfigRegistry.getConfig("mutations.yml"); + crops = GardenConfigRegistry.getMapList(greenhouse, "crops"); + harvestBounty = GardenConfigRegistry.getMapList(greenhouse, "harvest_bounty"); + dnaMilestones = GardenConfigRegistry.getMapList(greenhouse, "dna_milestones"); + + mutationVineChances.clear(); + GardenConfigRegistry.getSection(greenhouse, "ethereal_vine_chances") + .forEach((key, value) -> mutationVineChances.put(key, Double.parseDouble(String.valueOf(value)))); + + greenhouseUnlockCosts.clear(); + for (Map unlock : GardenConfigRegistry.getMapList(greenhouse, "greenhouse_unlock_costs")) { + greenhouseUnlockCosts.put( + GardenConfigRegistry.getInt(unlock, "greenhouse", 1), + GardenConfigRegistry.getInt(unlock, "ethereal_vines", 0) + ); + } + } + + public int getUnlockCostForGreenhouse(int greenhouseIndex) { + return greenhouseUnlockCosts.getOrDefault(greenhouseIndex, 0); + } + + public double getMutationVineChance(String rarity) { + return mutationVineChances.getOrDefault(rarity, 0D); + } + + public List> getCrops() { + return crops; + } + + public List> getHarvestBounty() { + return harvestBounty; + } + + public List> getDnaMilestones() { + return dnaMilestones; + } + + public List> getMutations() { + return GardenConfigRegistry.getMapList(mutations, "mutations"); + } + + public int getUnlockGardenLevel() { + return GardenConfigRegistry.getInt(greenhouse, "unlock_garden_level", 7); + } + + public String getUnlockVisitor() { + return GardenConfigRegistry.getString(greenhouse, "unlock_visitor", "CARPENTER"); + } + + public String getUnlockItem() { + return GardenConfigRegistry.getString(greenhouse, "unlock_item", "GREENHOUSE_BLUEPRINT"); + } + + public int getMaxGreenhouses() { + return greenhouseUnlockCosts.keySet().stream().mapToInt(Integer::intValue).max().orElse(1); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIAnita.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIAnita.java new file mode 100644 index 0000000000..d147721f76 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIAnita.java @@ -0,0 +1,59 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class GUIAnita extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Anita", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + + layout.slot(20, ItemStackCreator.getStack( + "§aContest Overview", + Material.WHEAT, + 1, + "§7Jacob's Tickets: §a" + GardenGuiSupport.personal(player).getJacobsTickets(), + "§6Gold Medals: §6" + GardenGuiSupport.getMedalCount(player, "gold"), + "§fSilver Medals: §f" + GardenGuiSupport.getMedalCount(player, "silver"), + "§cBronze Medals: §c" + GardenGuiSupport.getMedalCount(player, "bronze"), + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIJacobSFarmingContests())); + + layout.slot(22, ItemStackCreator.getStack( + "§aPersonal Bests", + Material.PAPER, + 1, + "§7Tracked crop personal bests:", + "§e" + GardenGuiSupport.personal(player).getContestPersonalBests().size(), + "", + "§7Anita purchases tracked:", + "§e" + GardenGuiSupport.personal(player).getAnitaPurchases().size() + )); + + layout.slot(24, ItemStackCreator.getStackHead( + "§aGarden Chips", + "560aa469cc6b667dbcbfdc63e827b7c05ca7726af8a178a4aa2e8ffa2690e843", + 1, + "§7Manage redeemed Chips and spend", + "§2Sowdust §7on upgrades.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIManageChips())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIBarnSkins.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIBarnSkins.java new file mode 100644 index 0000000000..5ada2b8fa5 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIBarnSkins.java @@ -0,0 +1,99 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.swofty.type.garden.config.GardenBarnSkinDefinition; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIBarnSkins extends StatelessView { + private static final int[] SKIN_SLOTS = {10, 11, 12, 13, 14, 15}; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Barn Skins", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + SkyBlockGarden garden = GardenGuiSupport.garden(player); + List skins = garden == null ? List.of() : garden.getPlotService().getBarnSkins(); + + for (int index = 0; index < Math.min(skins.size(), SKIN_SLOTS.length); index++) { + GardenBarnSkinDefinition skin = skins.get(index); + int slot = SKIN_SLOTS[index]; + layout.slot(slot, (s, c) -> buildSkinItem((SkyBlockPlayer) c.player(), skin), (click, c) -> { + SkyBlockPlayer target = (SkyBlockPlayer) c.player(); + SkyBlockGarden targetGarden = GardenGuiSupport.garden(target); + if (targetGarden == null) { + target.sendMessage("§cYour Garden is not ready yet."); + return; + } + + GardenData.GardenCoreData core = GardenGuiSupport.core(target); + if (!GardenGuiSupport.isBarnSkinUnlocked(target, skin)) { + target.sendMessage("§cYou haven't unlocked that Barn Skin yet."); + return; + } + if (targetGarden.isBarnSwapInProgress()) { + target.sendMessage("§cA Barn Skin swap is already in progress."); + return; + } + if (core.getSelectedBarnSkin().equalsIgnoreCase(skin.id())) { + target.sendMessage("§eThat Barn Skin is already selected."); + return; + } + + target.closeInventory(); + target.sendMessage("§aSwapping to " + skin.displayName() + "§a..."); + targetGarden.changeBarnSkin(skin.id()).thenRun(() -> { + core.setSelectedBarnSkin(skin.id()); + core.getOwnedBarnSkins().add(skin.id()); + target.sendMessage("§aYour Barn Skin is now " + skin.displayName() + "§a."); + }).exceptionally(throwable -> { + target.sendMessage("§cFailed to swap Barn Skin: " + throwable.getMessage()); + return null; + }); + }); + } + } + + private net.minestom.server.item.ItemStack.Builder buildSkinItem(SkyBlockPlayer player, GardenBarnSkinDefinition skin) { + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + boolean selected = core.getSelectedBarnSkin().equalsIgnoreCase(skin.id()); + boolean unlocked = GardenGuiSupport.isBarnSkinUnlocked(player, skin); + List lore = new ArrayList<>(List.of( + "§7Select this skin for your Barn!", + "" + )); + lore.add(GardenGuiSupport.colorForRarity(skin.rarity()) + "§l" + skin.rarity() + " COSMETIC"); + lore.add(""); + if (selected) { + lore.add("§aSELECTED"); + } else if (unlocked) { + lore.add("§eClick to select!"); + } else { + lore.add("§7Unlock source: §b" + skin.unlockSource()); + lore.add(""); + lore.add("§c§lLOCKED"); + } + return GardenGuiSupport.itemWithLore( + skin.id(), + GardenGuiSupport.colorForRarity(skin.rarity()) + skin.displayName().replace(" Barn Skin", ""), + lore + ); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIComposter.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIComposter.java new file mode 100644 index 0000000000..514919e45f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIComposter.java @@ -0,0 +1,341 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class GUIComposter extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Composter", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenGuiSupport.syncComposter(player); + GardenData.GardenComposterData data = GardenGuiSupport.composter(player); + int organicMatterCap = GardenServices.composter().getOrganicMatterCapacity(data.getUpgrades().getOrDefault("organic_matter_cap", 0)); + int fuelCap = GardenServices.composter().getFuelCapacity(data.getUpgrades().getOrDefault("fuel_cap", 0)); + + String organicMatterBar = GardenGuiSupport.progressBar(data.getOrganicMatter(), organicMatterCap) + + " §e" + StringUtility.commaify(Math.round(data.getOrganicMatter() * 10D) / 10D) + + "§6/§e" + StringUtility.commaify(organicMatterCap); + String fuelBar = GardenGuiSupport.progressBar(data.getFuel(), fuelCap) + + " §e" + StringUtility.commaify(Math.round(data.getFuel())) + + "§6/§e" + StringUtility.commaify(fuelCap); + + layout.slot(1, buildMeterItem("§eOrganic Matter", organicMatterFill(data.getOrganicMatter(), organicMatterCap), organicMatterBar)); + layout.slot(7, buildMeterItem("§2Fuel", fuelFill(data.getFuel(), fuelCap), fuelBar)); + layout.slot(10, buildMeterItem("§eOrganic Matter", organicMatterFill(data.getOrganicMatter(), organicMatterCap), organicMatterBar)); + layout.slot(16, buildMeterItem("§2Fuel", fuelFill(data.getFuel(), fuelCap), fuelBar)); + layout.slot(19, buildMeterItem("§eOrganic Matter", organicMatterFill(data.getOrganicMatter(), organicMatterCap), organicMatterBar)); + layout.slot(25, buildMeterItem("§2Fuel", fuelFill(data.getFuel(), fuelCap), fuelBar)); + layout.slot(28, buildMeterItem("§eOrganic Matter", organicMatterFill(data.getOrganicMatter(), organicMatterCap), organicMatterBar)); + layout.slot(34, buildMeterItem("§2Fuel", fuelFill(data.getFuel(), fuelCap), fuelBar)); + layout.slot(37, buildMeterItem("§eOrganic Matter", organicMatterFill(data.getOrganicMatter(), organicMatterCap), organicMatterBar)); + layout.slot(43, buildMeterItem("§2Fuel", fuelFill(data.getFuel(), fuelCap), fuelBar)); + + layout.slot(13, (s, c) -> ItemStackCreator.getStack( + "§eCollect Compost", + data.getCompostAvailable() > 0 ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA, + 1, + "§7Compost Available: §a" + data.getCompostAvailable(), + "", + data.getCompostAvailable() > 0 ? "§eClick to collect!" : "§cNo Compost to collect" + ), (click, c) -> { + if (data.getCompostAvailable() <= 0) { + return; + } + int available = data.getCompostAvailable(); + data.setCompostAvailable(0); + player.addAndUpdateItem(ItemType.COMPOST, available); + player.playSuccessSound(); + c.session(Object.class).refresh(); + }); + + layout.slot(22, ItemStackCreator.getStack( + "§aComposter Upgrades", + Material.HOPPER, + 1, + "§7Upgrade your composter to increase", + "§7your compost production.", + "", + "§7Speed: §e" + data.getUpgrades().getOrDefault("speed", 0), + "§7Multi Drop: §e" + data.getUpgrades().getOrDefault("multi_drop", 0), + "§7Fuel Cap: §e" + data.getUpgrades().getOrDefault("fuel_cap", 0), + "§7Organic Matter Cap: §e" + data.getUpgrades().getOrDefault("organic_matter_cap", 0), + "§7Cost Reduction: §e" + data.getUpgrades().getOrDefault("cost_reduction", 0), + "", + "§eClick to view upgrades!" + )); + + layout.slot(39, ItemStackCreator.getStack( + "§aInsert Crops from Sacks", + Material.CAULDRON, + 1, + "§7Grab as many crops that will fit into", + "§7the composter from your sacks.", + "", + "§7In your sacks: §e" + StringUtility.commaify(getTotalOrganicMatterInSacks(player)) + " Organic Matter", + "", + "§eLeft-click to grab from sacks!" + ), (click, c) -> { + int moved = insertOrganicMatter(player, true); + if (moved > 0) { + player.playSuccessSound(); + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cNo organic matter items could be inserted from sacks."); + } + }); + + layout.slot(41, ItemStackCreator.getStack( + "§aInsert Fuel from Sacks", + Material.CAULDRON, + 1, + "§7Grab as much fuel that will fit into", + "§7the composter from your sacks.", + "", + "§7In your sacks: §e" + StringUtility.commaify(getTotalFuelInSacks(player)) + " Fuel", + "", + "§eLeft-click to grab from sacks!" + ), (click, c) -> { + int moved = insertFuel(player, true); + if (moved > 0) { + player.playSuccessSound(); + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cNo fuel items could be inserted from sacks."); + } + }); + + layout.slot(48, ItemStackCreator.getStackHead( + "§aInsert Crops from Inventory", + "ef835b8941fe319931749b87fe8e84c5d1f4a271b5fbce5e700a60004d881f79", + 1, + "§7Grab as many crops that will fit into", + "§7the composter from your inventory.", + "", + "§7In your inventory: §e" + StringUtility.commaify(getTotalOrganicMatterInInventory(player)) + " Organic Matter", + "", + "§eLeft-click to grab from inventory!" + ), (click, c) -> { + int moved = insertOrganicMatter(player, false); + if (moved > 0) { + player.playSuccessSound(); + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cNo organic matter items could be inserted from your inventory."); + } + }); + + layout.slot(50, ItemStackCreator.getStack( + "§aInsert Fuel from Inventory", + Material.GREEN_DYE, + 1, + "§7Grab as much fuel that will fit into", + "§7the composter from your inventory.", + "", + "§7In your inventory: §e" + StringUtility.commaify(getTotalFuelInInventory(player)) + " Fuel", + "", + "§eLeft-click to grab from inventory!" + ), (click, c) -> { + int moved = insertFuel(player, false); + if (moved > 0) { + player.playSuccessSound(); + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cNo fuel items could be inserted from your inventory."); + } + }); + + layout.slot(46, ItemStackCreator.getStack( + "§eCrop Meter", + Material.POTATO, + 1, + organicMatterBar, + "", + "§7Fill your composter with §acrops§7, like", + "§fWheat §7or §aEnchanted Potato§7, to turn", + "§7them into §eOrganic Matter§7. Organic", + "§7Matter is used to make §6Compost§7.", + "", + "§7The composter must have §b4,000", + "§7organic matter stored to start", + "§7making compost." + )); + + layout.slot(52, ItemStackCreator.getStackHead( + "§2Fuel Meter", + "d5d2750595477ecc13869580b12ffc3b13fc2b3ac3e5035ecfc9aafa036722a2", + 1, + fuelBar, + "", + "§7Fill your composter with §2machine fuel§7,", + "§7like §9Biofuel§7 to power the composter", + "§7to turn Organic Matter into §6Compost§7.", + "", + "§7The composter must have §22,000 Fuel", + "§7stored to start making compost." + )); + } + + private net.minestom.server.item.ItemStack.Builder buildMeterItem(String name, Material material, String bar) { + return ItemStackCreator.getStack(name, material, 1, bar); + } + + private int insertOrganicMatter(SkyBlockPlayer player, boolean fromSacks) { + return insertValues( + player, + GardenServices.composter().getOrganicMatterCapacity(GardenGuiSupport.composter(player).getUpgrades().getOrDefault("organic_matter_cap", 0)), + GardenGuiSupport.composter(player).getOrganicMatter(), + false, + fromSacks + ); + } + + private int insertFuel(SkyBlockPlayer player, boolean fromSacks) { + return insertValues( + player, + GardenServices.composter().getFuelCapacity(GardenGuiSupport.composter(player).getUpgrades().getOrDefault("fuel_cap", 0)), + GardenGuiSupport.composter(player).getFuel(), + true, + fromSacks + ); + } + + private int insertValues(SkyBlockPlayer player, double capacity, double current, boolean fuel, boolean fromSacks) { + GardenData.GardenComposterData data = GardenGuiSupport.composter(player); + double remainingCapacity = capacity - current; + if (remainingCapacity <= 0D) { + return 0; + } + + List> values = (fuel + ? GardenServices.composter().getFuelValues() + : GardenServices.composter().getOrganicMatterValues()) + .entrySet().stream() + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) + .toList(); + + int insertedStacks = 0; + double insertedValue = 0D; + Map tracker = fuel ? data.getInsertedFuel() : data.getInsertedMatter(); + for (Map.Entry entry : values) { + if (remainingCapacity <= 0D) { + break; + } + + ItemType type = ItemType.get(entry.getKey()); + double perItem = entry.getValue(); + if (type == null || perItem <= 0D) { + continue; + } + + int available = fromSacks ? player.getSackItems().getAmount(type) : player.getAmountInInventory(type); + if (available <= 0) { + continue; + } + + int insertable = (int) Math.min(available, Math.floor(remainingCapacity / perItem)); + if (insertable <= 0) { + continue; + } + + if (fromSacks) { + player.getSackItems().decrease(type, insertable); + } else if (player.takeItem(type, insertable) == null) { + continue; + } + + insertedStacks += insertable; + double valueAdded = insertable * perItem; + insertedValue += valueAdded; + remainingCapacity -= valueAdded; + tracker.merge(entry.getKey(), (long) insertable, Long::sum); + } + + if (fuel) { + data.setFuel(data.getFuel() + insertedValue); + } else { + data.setOrganicMatter(data.getOrganicMatter() + insertedValue); + } + return insertedStacks; + } + + private double getTotalOrganicMatterInSacks(SkyBlockPlayer player) { + return GardenServices.composter().getOrganicMatterValues().entrySet().stream() + .mapToDouble(entry -> { + ItemType type = ItemType.get(entry.getKey()); + return type == null ? 0D : player.getSackItems().getAmount(type) * entry.getValue(); + }) + .sum(); + } + + private double getTotalFuelInSacks(SkyBlockPlayer player) { + return GardenServices.composter().getFuelValues().entrySet().stream() + .mapToDouble(entry -> { + ItemType type = ItemType.get(entry.getKey()); + return type == null ? 0D : player.getSackItems().getAmount(type) * entry.getValue(); + }) + .sum(); + } + + private double getTotalOrganicMatterInInventory(SkyBlockPlayer player) { + return GardenServices.composter().getOrganicMatterValues().entrySet().stream() + .mapToDouble(entry -> { + ItemType type = ItemType.get(entry.getKey()); + return type == null ? 0D : player.getAmountInInventory(type) * entry.getValue(); + }) + .sum(); + } + + private double getTotalFuelInInventory(SkyBlockPlayer player) { + return GardenServices.composter().getFuelValues().entrySet().stream() + .mapToDouble(entry -> { + ItemType type = ItemType.get(entry.getKey()); + return type == null ? 0D : player.getAmountInInventory(type) * entry.getValue(); + }) + .sum(); + } + + private Material organicMatterFill(double current, double cap) { + double ratio = cap <= 0D ? 0D : current / cap; + if (ratio >= 0.75D) { + return Material.LIME_STAINED_GLASS_PANE; + } + if (ratio >= 0.25D) { + return Material.YELLOW_STAINED_GLASS_PANE; + } + return Material.RED_STAINED_GLASS_PANE; + } + + private Material fuelFill(double current, double cap) { + double ratio = cap <= 0D ? 0D : current / cap; + if (ratio >= 0.75D) { + return Material.LIME_STAINED_GLASS_PANE; + } + if (ratio >= 0.25D) { + return Material.YELLOW_STAINED_GLASS_PANE; + } + return Material.RED_STAINED_GLASS_PANE; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIConfigurePlots.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIConfigurePlots.java new file mode 100644 index 0000000000..b35842ffc0 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIConfigurePlots.java @@ -0,0 +1,130 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.config.GardenPlotDefinition; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; +import java.util.Map; + +public class GUIConfigurePlots extends StatelessView { + private static final int[] PLOT_SLOTS = { + 2, 3, 4, 5, 6, + 11, 12, 13, 14, 15, + 20, 21, 22, 23, 24, + 29, 30, 31, 32, 33, + 38, 39, 40, 41, 42 + }; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Configure Plots", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + List plots = GardenGuiSupport.lockedPlotsFirst(player); + for (int i = 0; i < Math.min(PLOT_SLOTS.length, plots.size()); i++) { + GardenPlotDefinition plot = plots.get(i); + int slot = PLOT_SLOTS[i]; + layout.slot(slot, buildPlotItem(player, core, plot)); + } + } + + private net.minestom.server.item.ItemStack.Builder buildPlotItem(SkyBlockPlayer player, GardenData.GardenCoreData core, GardenPlotDefinition plot) { + boolean unlocked = plot.defaultUnlocked() || core.getUnlockedPlots().contains(plot.id()); + Material material = unlocked ? plotMaterial(core, plot) : Material.OAK_BUTTON; + String plotNumber = plot.id().replaceAll("\\D+", ""); + String displayNumber = plotNumber.isBlank() ? plot.id().toUpperCase() : plotNumber; + + if ("central".equalsIgnoreCase(plot.id())) { + return ItemStackCreator.getStack( + "§aThe Barn", + Material.LIGHT_BLUE_TERRACOTTA, + 1, + "", + "§eRight-click to teleport to this plot!" + ); + } + + if (!unlocked) { + long copperLikeCost = plotCost(plot); + return ItemStackCreator.getStack( + "§ePlot §7- §b" + displayNumber, + material, + 1, + "§7Requirement", + "§a✔ Garden Level " + plot.gardenLevel(), + "", + "§7Cost:", + costText(copperLikeCost), + "", + "§7Rewards:", + "§8+§a3 §6☘ Farming Fortune", + "§8+§b5 SkyBlock XP", + "", + "§cYou need more Compost to unlock this!" + ); + } + + String preset = StringUtility.toNormalCase(core.getPlotPresets().getOrDefault(plot.id(), plot.displayName())); + double cleaned = core.getCleanedPlots().getOrDefault(plot.id(), 0D); + return ItemStackCreator.getStack( + "§aPlot §7- §b" + displayNumber, + material, + 1, + "§7Preset: §a" + preset, + cleaned >= 70D ? "§aCleaned: §e" + String.format("%.1f", cleaned) + "%" : "§7Cleaned: §e" + String.format("%.1f", cleaned) + "%", + "", + "§eLeft-click to modify!", + "§eRight-click to teleport to this plot!" + ); + } + + private Material plotMaterial(GardenData.GardenCoreData core, GardenPlotDefinition plot) { + String preset = core.getPlotPresets().getOrDefault(plot.id(), "").toUpperCase(); + return switch (preset) { + case "SUGAR_CANE" -> Material.SUGAR_CANE; + case "POTATO" -> Material.POTATO; + case "MUSHROOM" -> Material.RED_MUSHROOM; + case "WILD_ROSE" -> Material.ROSE_BUSH; + case "GREENHOUSE" -> Material.WHITE_STAINED_GLASS; + default -> Material.CARVED_PUMPKIN; + }; + } + + private long plotCost(GardenPlotDefinition plot) { + Map config = GardenConfigRegistry.getConfig("plots.yml"); + for (Map rawPlot : GardenConfigRegistry.getMapList(config, "plots")) { + if (plot.id().equalsIgnoreCase(GardenConfigRegistry.getString(rawPlot, "id", ""))) { + return GardenConfigRegistry.getLong(rawPlot, "cost", 0L); + } + } + return 0L; + } + + private String costText(long cost) { + if (cost <= 0L) { + return "§aFree"; + } + if (cost >= 1_000L) { + return "§9Compost Bundle §8x" + (cost / 1_000L); + } + return "§aCompost §8x" + cost; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropMilestones.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropMilestones.java new file mode 100644 index 0000000000..a86f7fd2c6 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropMilestones.java @@ -0,0 +1,91 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.milestone.GardenMilestoneService; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUICropMilestones extends StatelessView { + private static final int[] CROP_SLOTS = { + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24 + }; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Crop Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + List crops = GardenServices.milestones().getCropDefinitions(); + + int completedTiers = GardenGuiSupport.core(player).getCropMilestones().values().stream() + .mapToInt(Integer::intValue) + .sum(); + long harvestedCrops = GardenGuiSupport.core(player).getCropMilestoneProgress().values().stream() + .mapToLong(Long::longValue) + .sum(); + + layout.slot(4, ItemStackCreator.getStack( + "§aCrop Milestones", + net.minestom.server.item.Material.WHEAT, + 1, + "§7Harvest crops on your Garden to", + "§7unlock milestone tiers for each crop.", + "", + "§7Tracked crops: §e" + crops.size(), + "§7Total harvested: §e" + StringUtility.commaify(harvestedCrops), + "§7Completed tiers: §e" + StringUtility.commaify(completedTiers) + )); + + for (int index = 0; index < Math.min(crops.size(), CROP_SLOTS.length); index++) { + GardenMilestoneService.CropMilestoneDefinition definition = crops.get(index); + GardenMilestoneService.MilestoneProgress progress = GardenServices.milestones().getCropProgress(player, definition.id()); + List lore = new ArrayList<>(); + lore.add("§7Tier: §e" + progress.completedTiers() + "§7/§a" + definition.tiers().size()); + lore.add("§7Harvested: §e" + StringUtility.commaify(progress.progress())); + lore.add(""); + + if (progress.nextTier() != null) { + long progressIntoTier = Math.max(0L, progress.progress() - progress.previousThreshold()); + long neededThisTier = Math.max(1L, progress.nextThreshold() - progress.previousThreshold()); + long remaining = Math.max(0L, progress.nextThreshold() - progress.progress()); + lore.add("§7Progress to Tier §a" + StringUtility.getAsRomanNumeral(progress.nextTier().tier()) + "§7:"); + lore.add(GardenGuiSupport.progressBar(progressIntoTier, neededThisTier)); + lore.add("§7Remaining: §e" + StringUtility.commaify(remaining)); + lore.add(""); + lore.add("§7Next Rewards:"); + lore.add(" §8+§3" + StringUtility.commaify(progress.nextTier().farmingXp()) + " Farming XP"); + lore.add(" §8+§2" + progress.nextTier().gardenXp() + " Garden XP"); + lore.add(" §8+§b" + progress.nextTier().skyblockXp() + " SkyBlock XP"); + } else { + lore.add("§aMAXED"); + lore.add(""); + lore.add("§7You have completed every"); + lore.add("§7milestone tier for this crop."); + } + + layout.slot(CROP_SLOTS[index], GardenGuiSupport.itemWithLore( + definition.iconItemId(), + "§a" + definition.displayName(), + lore + )); + } + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropUpgrades.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropUpgrades.java new file mode 100644 index 0000000000..84b4923fec --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUICropUpgrades.java @@ -0,0 +1,114 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GUICropUpgrades extends StatelessView { + private static final int[] CROP_SLOTS = { + 11, 12, 13, 14, 15, + 20, 21, 22, 23, 24, + 30, 31, 32 + }; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Crop Upgrades", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + List crops = GardenConfigRegistry.getList( + GardenConfigRegistry.getConfig("crop_upgrades.yml"), + "crops" + ).stream().map(String::valueOf).toList(); + + for (int index = 0; index < Math.min(crops.size(), CROP_SLOTS.length); index++) { + String cropId = crops.get(index); + int slot = CROP_SLOTS[index]; + layout.slot(slot, (s, c) -> buildCropItem((SkyBlockPlayer) c.player(), cropId), (click, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + int currentTier = core.getCropUpgrades().getOrDefault(cropId, 0); + Map nextTier = nextTier(currentTier + 1); + if (nextTier.isEmpty()) { + player.sendMessage("§eThat crop is already maxed."); + return; + } + + int requiredGardenLevel = GardenConfigRegistry.getInt(nextTier, "garden_level", 0); + int copperCost = GardenConfigRegistry.getInt(nextTier, "copper", 0); + if (core.getLevel() < requiredGardenLevel) { + player.sendMessage("§cYou need Garden Level " + StringUtility.getAsRomanNumeral(requiredGardenLevel) + " first."); + return; + } + if (core.getCopper() < copperCost) { + player.sendMessage("§cYou don't have enough Copper."); + return; + } + + core.setCopper(core.getCopper() - copperCost); + core.getCropUpgrades().put(cropId, currentTier + 1); + player.playSuccessSound(); + c.session(Object.class).refresh(); + }); + } + } + + private net.minestom.server.item.ItemStack.Builder buildCropItem(SkyBlockPlayer player, String cropId) { + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + int currentTier = core.getCropUpgrades().getOrDefault(cropId, 0); + int fortune = currentTier * net.swofty.type.garden.GardenServices.desk().getCropUpgradeFortunePerTier(); + List lore = new ArrayList<>(List.of( + "§7Upgrade your §a" + StringUtility.toNormalCase(cropId) + " §7tier to", + "§7increase your §6☘ " + StringUtility.toNormalCase(cropId) + " Fortune§7.", + "", + "§7Current Tier: §e" + currentTier + "§7/§a9", + "§7" + StringUtility.toNormalCase(cropId) + " Fortune: §6+" + fortune + "☘", + "" + )); + + Map nextTier = nextTier(currentTier + 1); + if (nextTier.isEmpty()) { + lore.add("§a§lMAXED"); + } else { + int requiredGardenLevel = GardenConfigRegistry.getInt(nextTier, "garden_level", 0); + int copperCost = GardenConfigRegistry.getInt(nextTier, "copper", 0); + lore.add("§7Next Tier: §e" + (currentTier + 1)); + lore.add("§7Requires Garden Level §a" + StringUtility.getAsRomanNumeral(requiredGardenLevel)); + lore.add("§7Cost: §c" + StringUtility.commaify(copperCost) + " Copper"); + lore.add(""); + lore.add(core.getCopper() >= copperCost && core.getLevel() >= requiredGardenLevel + ? "§eClick to upgrade!" + : "§cRequirements not met"); + } + + return GardenGuiSupport.itemWithLore( + cropId, + "§a" + StringUtility.toNormalCase(cropId), + lore + ); + } + + private Map nextTier(int tier) { + return net.swofty.type.garden.GardenServices.desk().getCropUpgradeTiers().stream() + .filter(entry -> GardenConfigRegistry.getInt(entry, "tier", 0) == tier) + .findFirst() + .orElse(Map.of()); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIDesk.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIDesk.java new file mode 100644 index 0000000000..591e5fd25b --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIDesk.java @@ -0,0 +1,152 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GUIDesk extends StatelessView { + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Desk", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 49); + Components.fill(layout); + + layout.slot(4, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + int nextLevel = Math.min(core.getLevel() + 1, 15); + Map next = net.swofty.type.garden.GardenServices.levels().getLevel(nextLevel); + + List lore = new ArrayList<>(List.of( + "§7Earn Garden experience by", + "§7accepting visitors' offers and", + "§7unlocking new milestones!", + "", + "§7Garden XP: §e" + StringUtility.commaify(core.getExperience()) + )); + + if (core.getLevel() < 15 && !next.isEmpty()) { + lore.add("§7Level " + StringUtility.getAsRomanNumeral(nextLevel) + " Rewards:"); + for (String reward : net.swofty.type.garden.GardenServices.levels().getRewardsForLevel(nextLevel)) { + lore.add(" §8+§7" + reward); + } + lore.add(""); + } + + lore.add("§7You currently have §2" + + (core.getLevel() * net.swofty.type.garden.GardenServices.levels().getCropGrowthPerLevelPercent()) + + " Crop Growth§7!"); + lore.add(""); + lore.add("§eClick to view!"); + + return ItemStackCreator.getStack( + "§aGarden Level " + StringUtility.getAsRomanNumeral(core.getLevel()), + Material.SUNFLOWER, + 1, + lore + ); + }, (click, c) -> c.push(new GUIGardenLevels())); + + layout.slot(19, ItemStackCreator.getStack( + "§aConfigure Plots", + Material.GRASS_BLOCK, + 1, + "§7Unlock access to new plots or modify", + "§7plots that you have already unlocked!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIConfigurePlots())); + + layout.slot(21, ItemStackCreator.getStack( + "§aGarden Upgrades", + Material.GLISTERING_MELON_SLICE, + 1, + "§7Upgrade various aspects of your", + "§7garden to increase yield,", + "§7experience, and more.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIGardenUpgrades())); + + layout.slot(23, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + return ItemStackCreator.getStack( + "§aSkyMart", + Material.EMERALD, + 1, + "§7Browse the wide variety of products", + "§7SkyMart has to offer. We are not", + "§7responsible for any injuries,", + "§7accidents, headaches, paper-cuts or", + "§7sudden outburst of tears. SkyMart", + "§7wishes you happy shopping.", + "", + "§7Copper: §c" + StringUtility.commaify(GardenGuiSupport.core(player).getCopper()), + "", + "§eClick to view!" + ); + }, (click, c) -> c.push(new GUISkyMart())); + + layout.slot(25, ItemStackCreator.getStack( + "§aGarden Milestones", + Material.GOLD_BLOCK, + 1, + "§7Achieve milestones on your Garden", + "§7to earn Garden XP and Farming XP.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIGardenMilestones())); + + layout.slot(31, ItemStackCreator.getStack( + "§aGarden Skins", + Material.BEACON, + 1, + "§7View and select different skins for", + "§7your Garden!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIGardenSkins())); + + layout.slot(50, (s, c) -> { + SkyBlockPlayer current = (SkyBlockPlayer) c.player(); + String mode = GardenGuiSupport.core(current).getSelectedTimeMode(); + return ItemStackCreator.getStack( + "§aGarden Time", + Material.CLOCK, + 1, + "§7Modifies your Garden time.", + "", + "§7Current mode: §e" + StringUtility.toNormalCase(mode) + ); + }, (click, c) -> { + SkyBlockPlayer current = (SkyBlockPlayer) c.player(); + GardenData.GardenCoreData core = GardenGuiSupport.core(current); + String next = switch (core.getSelectedTimeMode()) { + case "DAY" -> "NIGHT"; + case "NIGHT" -> "DYNAMIC"; + default -> "DAY"; + }; + core.setSelectedTimeMode(next); + c.session(Object.class).refresh(); + }); + } + +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenLevels.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenLevels.java new file mode 100644 index 0000000000..6cfd4861dd --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenLevels.java @@ -0,0 +1,93 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GUIGardenLevels extends StatelessView { + private static final int[] LEVEL_SLOTS = { + 9, 18, 27, 28, 29, + 20, 11, 2, 3, 4, + 13, 22, 31, 32, 33 + }; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Levels", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + layout.slot(0, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + int level = GardenGuiSupport.core(player).getLevel(); + return ItemStackCreator.getStack( + "§aGarden Levels", + Material.SUNFLOWER, + 1, + "§7Earn Garden experience by", + "§7accepting visitors' offers and", + "§7unlocking new milestones!", + "", + "§7Current level: §e" + StringUtility.getAsRomanNumeral(level), + "§7Garden XP: §e" + StringUtility.commaify(GardenGuiSupport.core(player).getExperience()), + "", + "§8Increase your Garden Level to", + "§8unlock new visitors, crops and more!" + ); + }); + + List> levels = net.swofty.type.garden.GardenServices.levels().getLevels(); + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + int currentLevel = GardenGuiSupport.core(player).getLevel(); + for (int index = 0; index < Math.min(levels.size(), LEVEL_SLOTS.length); index++) { + Map level = levels.get(index); + int levelNumber = net.swofty.type.garden.config.GardenConfigRegistry.getInt(level, "level", index + 1); + int slot = LEVEL_SLOTS[index]; + layout.slot(slot, buildLevelItem(levelNumber, currentLevel, level)); + } + } + + private net.minestom.server.item.ItemStack.Builder buildLevelItem(int levelNumber, int currentLevel, Map level) { + boolean unlocked = currentLevel > levelNumber; + boolean current = currentLevel == levelNumber; + String color = current ? "§e" : unlocked ? "§a" : "§c"; + Material material = current ? Material.DIAMOND_HOE : unlocked ? Material.LIME_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE; + List lore = new ArrayList<>(); + lore.add("§7Rewards:"); + for (String reward : net.swofty.type.garden.GardenServices.levels().getRewardsForLevel(levelNumber)) { + lore.add(" §8+§7" + reward); + } + lore.add(""); + if (current) { + lore.add("§e§lCURRENT LEVEL"); + } else if (unlocked) { + lore.add("§a§lUNLOCKED"); + } else { + lore.add("§7Requires: §aGarden Level " + StringUtility.getAsRomanNumeral(levelNumber)); + lore.add(""); + lore.add("§c§lLOCKED"); + } + return ItemStackCreator.getStack( + color + "Garden Level " + StringUtility.getAsRomanNumeral(levelNumber), + material, + 1, + lore + ); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenMilestones.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenMilestones.java new file mode 100644 index 0000000000..eda1782dcb --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenMilestones.java @@ -0,0 +1,51 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class GUIGardenMilestones extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + + layout.slot(21, ItemStackCreator.getStack( + "§aCrop Milestones", + Material.WHEAT, + 1, + "§7View progress and rewards for", + "§7crop milestones on your garden!", + "", + "§7Tracked crops: §e" + core.getCropMilestones().size(), + "§eClick to view!" + ), (click, c) -> c.push(new GUICropMilestones())); + + layout.slot(23, ItemStackCreator.getStackHead( + "§aVisitor Milestones", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7View progress and rewards for", + "§7visitor milestones on your garden!", + "", + "§7Tracked visitors: §e" + core.getVisitorMilestones().size(), + "§eClick to view!" + ), (click, c) -> c.push(new GUIVisitorMilestones())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenSkins.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenSkins.java new file mode 100644 index 0000000000..391d4adfaf --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenSkins.java @@ -0,0 +1,45 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; + +public class GUIGardenSkins extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Skins", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 31, ctx); + + layout.slot(11, ItemStackCreator.getStack( + "§aBarn Skins", + Material.OAK_PLANKS, + 1, + "§7View and select different skins for", + "§7your Barn.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIBarnSkins())); + + layout.slot(15, ItemStackCreator.getStack( + "§aGreenhouse Skins", + Material.WHITE_STAINED_GLASS, + 1, + "§7Select unlocked Greenhouse skins", + "§7from your persisted Garden profile", + "§7cosmetics data.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIGreenhouseSkins())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenUpgrades.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenUpgrades.java new file mode 100644 index 0000000000..1468069b01 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGardenUpgrades.java @@ -0,0 +1,45 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; + +public class GUIGardenUpgrades extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Garden Upgrades", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 31, ctx); + + layout.slot(11, ItemStackCreator.getStack( + "§aCrop Upgrades", + Material.WHEAT, + 1, + "§7Increase your §6☘ Farming Fortune", + "§7for each crop by upgrading them!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUICropUpgrades())); + + layout.slot(15, ItemStackCreator.getStack( + "§aGreenhouse Upgrades", + Material.WHITE_STAINED_GLASS, + 1, + "§7Unlock Greenhouses, track mutation", + "§7analyzer progress, and review slot", + "§7and bounty data.", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUIGreenhouseUpgrades())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseSkins.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseSkins.java new file mode 100644 index 0000000000..a1b21ad8cd --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseSkins.java @@ -0,0 +1,78 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIGreenhouseSkins extends StatelessView { + private static final int[] SKIN_SLOTS = {10, 11, 12, 13, 14, 15, 16}; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Greenhouse Skins", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenGreenhouseData greenhouse = GardenGuiSupport.greenhouse(player); + List skins = new ArrayList<>(greenhouse.getOwnedGreenhouseSkins()); + if (!skins.contains("default")) { + skins.add(0, "default"); + } + skins.sort(String::compareToIgnoreCase); + + for (int index = 0; index < Math.min(skins.size(), SKIN_SLOTS.length); index++) { + String skin = skins.get(index); + layout.slot(SKIN_SLOTS[index], buildSkinItem(greenhouse, skin), (click, c) -> { + greenhouse.setSelectedGreenhouseSkin(skin); + player.playSuccessSound(); + c.session(Object.class).refresh(); + }); + } + + layout.slot(31, ItemStackCreator.getStack( + "§aUnlock Sources", + Material.PAPER, + 1, + "§7Owned Greenhouse skins: §e" + greenhouse.getOwnedGreenhouseSkins().size(), + "§7Selected: §a" + StringUtility.toNormalCase(greenhouse.getSelectedGreenhouseSkin()), + "", + "§7Greenhouse skins are profile data and", + "§7can be granted by future milestone,", + "§7SkyMart, or event unlock flows." + )); + } + + private net.minestom.server.item.ItemStack.Builder buildSkinItem(GardenData.GardenGreenhouseData greenhouse, String skin) { + boolean selected = greenhouse.getSelectedGreenhouseSkin().equalsIgnoreCase(skin); + List lore = new ArrayList<>(List.of( + "§7Select this skin for your Greenhouse.", + "", + "§f§lCOMMON COSMETIC", + "" + )); + lore.add(selected ? "§aCurrently selected!" : "§eClick to select!"); + return ItemStackCreator.getStack( + "§f" + StringUtility.toNormalCase(skin), + Material.WHITE_STAINED_GLASS, + 1, + lore + ); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseUpgrades.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseUpgrades.java new file mode 100644 index 0000000000..d0d09ad098 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIGreenhouseUpgrades.java @@ -0,0 +1,153 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIGreenhouseUpgrades extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Greenhouse Upgrades", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 31, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenGreenhouseData greenhouse = GardenGuiSupport.greenhouse(player); + + layout.slot(4, ItemStackCreator.getStack( + "§aGreenhouse Progress", + Material.WHITE_STAINED_GLASS, + 1, + "§7Blueprint unlocked: " + (greenhouse.isBlueprintUnlocked() ? "§aYes" : "§cNo"), + "§7Unlocked Greenhouses: §e" + greenhouse.getUnlockedGreenhouses() + "§7/§a" + net.swofty.type.garden.GardenServices.greenhouse().getMaxGreenhouses(), + "§7Unlocked crop slots: §e" + greenhouse.getUnlockedSlots().size(), + "§7Spent Ethereal Vines: §d" + greenhouse.getSpentEtherealVines(), + "", + "§7DNA milestone progress: §b" + greenhouse.getDnaMilestone() + "§7/§a" + net.swofty.type.garden.GardenServices.greenhouse().getDnaMilestones().size() + )); + + layout.slot(11, buildGreenhouseUnlock(player, greenhouse, 1), (click, c) -> tryUnlockGreenhouse(player, greenhouse, 1, c)); + layout.slot(13, buildGreenhouseUnlock(player, greenhouse, 2), (click, c) -> tryUnlockGreenhouse(player, greenhouse, 2, c)); + layout.slot(15, buildGreenhouseUnlock(player, greenhouse, 3), (click, c) -> tryUnlockGreenhouse(player, greenhouse, 3, c)); + + layout.slot(21, ItemStackCreator.getStack( + "§6Crop Slots", + Material.COARSE_DIRT, + 1, + "§7Unlocked slots: §a" + greenhouse.getUnlockedSlots().size(), + "§7Tracked plot unlocks: §e" + greenhouse.getUnlockedPlots(), + "", + "§7Use §5Ethereal Vines §7to unlock crop", + "§7slots once your Greenhouse is ready.", + "", + "§7The slot grid and mutation data are", + "§7now persisted in Garden profile data." + )); + + layout.slot(23, ItemStackCreator.getStack( + "§5Mutation Analyzer", + Material.SPYGLASS, + 1, + "§7Analyzed mutations: §a" + greenhouse.getAnalyzedMutations().size(), + "§7Tracked harvest counts: §e" + greenhouse.getMutationHarvests().size(), + "", + "§7Known mutations: §d" + net.swofty.type.garden.GardenServices.greenhouse().getMutations().size(), + "§7DNA milestones: §b" + net.swofty.type.garden.GardenServices.greenhouse().getDnaMilestones().size() + )); + + layout.slot(25, ItemStackCreator.getStack( + "§aHarvest Bounty", + Material.MOSS_BLOCK, + 1, + "§7Supported Greenhouse crops: §a" + net.swofty.type.garden.GardenServices.greenhouse().getCrops().size(), + "§7Harvest bounty drops: §e" + net.swofty.type.garden.GardenServices.greenhouse().getHarvestBounty().size(), + "", + "§7Crop, mutation, and bounty tables are", + "§7now loaded from §aconfiguration/skyblock/garden§7." + )); + } + + private net.minestom.server.item.ItemStack.Builder buildGreenhouseUnlock( + SkyBlockPlayer player, + GardenData.GardenGreenhouseData greenhouse, + int greenhouseIndex + ) { + int unlockCost = net.swofty.type.garden.GardenServices.greenhouse().getUnlockCostForGreenhouse(greenhouseIndex); + boolean unlocked = greenhouse.getUnlockedGreenhouses() >= greenhouseIndex; + boolean previousUnlocked = greenhouseIndex == 1 || greenhouse.getUnlockedGreenhouses() >= greenhouseIndex - 1; + List lore = new ArrayList<>(); + lore.add("§7Unlock greenhouse #" + greenhouseIndex + " for"); + lore.add("§7expanded mutation farming space."); + lore.add(""); + lore.add("§7Unlock cost: §5" + StringUtility.commaify(unlockCost) + " Ethereal Vines"); + lore.add("§7Your vines: §d" + StringUtility.commaify(player.getAmountInInventory(ItemType.ETHEREAL_VINE) + player.getSackItems().getAmount(ItemType.ETHEREAL_VINE))); + lore.add(""); + + if (unlocked) { + lore.add("§a§lUNLOCKED"); + } else if (!greenhouse.isBlueprintUnlocked()) { + lore.add("§cRequires the Greenhouse Blueprint"); + } else if (!previousUnlocked) { + lore.add("§cUnlock the previous Greenhouse first"); + } else { + lore.add(unlockCost == 0 ? "§eClick to unlock!" : "§eClick to spend Ethereal Vines!"); + } + + return ItemStackCreator.getStack( + unlocked ? "§aGreenhouse " + greenhouseIndex : "§eGreenhouse " + greenhouseIndex, + unlocked ? Material.LIME_STAINED_GLASS : Material.YELLOW_STAINED_GLASS, + greenhouseIndex, + lore + ); + } + + private void tryUnlockGreenhouse( + SkyBlockPlayer player, + GardenData.GardenGreenhouseData greenhouse, + int greenhouseIndex, + ViewContext ctx + ) { + if (greenhouse.getUnlockedGreenhouses() >= greenhouseIndex) { + player.sendMessage("§eThat Greenhouse is already unlocked."); + return; + } + if (!greenhouse.isBlueprintUnlocked()) { + player.sendMessage("§cYou need the Greenhouse Blueprint first."); + return; + } + if (greenhouseIndex > 1 && greenhouse.getUnlockedGreenhouses() < greenhouseIndex - 1) { + player.sendMessage("§cUnlock the previous Greenhouse first."); + return; + } + + int unlockCost = net.swofty.type.garden.GardenServices.greenhouse().getUnlockCostForGreenhouse(greenhouseIndex); + if (unlockCost > 0 && !player.removeItemFromPlayer(ItemType.ETHEREAL_VINE, unlockCost)) { + player.sendMessage("§cYou don't have enough Ethereal Vines."); + return; + } + + greenhouse.setBlueprintUnlocked(true); + greenhouse.setGreenhouseUnlocked(true); + greenhouse.setUnlockedGreenhouses(Math.max(greenhouse.getUnlockedGreenhouses(), greenhouseIndex)); + greenhouse.setSpentEtherealVines(greenhouse.getSpentEtherealVines() + unlockCost); + player.playSuccessSound(); + ctx.session(Object.class).refresh(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIJacobSFarmingContests.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIJacobSFarmingContests.java new file mode 100644 index 0000000000..1c97c0a9b0 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIJacobSFarmingContests.java @@ -0,0 +1,82 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIJacobSFarmingContests extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Jacob's Farming Contests", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 31, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + + layout.slot(11, ItemStackCreator.getStack( + "§aWhat is this?", + Material.OAK_SIGN, + 1, + "§8Instructions", + "", + "§7Every 3 SkyBlock days, Jacob holds a", + "§7contest for 3 §efarming §7collections.", + "", + "§7The contests last 1 SkyBlock day.", + "§8(20 minutes).", + "", + "§bCo-ops do NOT pool their collection!", + "", + "§7Requirement: §aFarming X", + "§7Requirement: §aTalk to Jacob", + "§7Reward floor: §e100 crops" + )); + + List scheduleLore = new ArrayList<>(List.of("§8Schedule", "")); + List upcoming = GardenGuiSupport.getUpcomingContests(3); + for (GardenGuiSupport.UpcomingContestDisplay display : upcoming) { + scheduleLore.add("§e" + GardenGuiSupport.formatContestDate(display)); + for (String crop : display.entry().crops()) { + scheduleLore.add("§e○ §7" + StringUtility.toNormalCase(crop)); + } + scheduleLore.add(""); + } + if (upcoming.isEmpty()) { + scheduleLore.add("§cContest service unavailable"); + } else { + scheduleLore.add("§8View this info in your full"); + scheduleLore.add("§8SkyBlock calendar!"); + } + layout.slot(13, ItemStackCreator.getStack("§6Upcoming Contests", Material.CLOCK, 1, scheduleLore)); + + layout.slot(15, ItemStackCreator.getStack( + "§bContest Rewards", + Material.WHEAT, + 1, + "§8Your progress", + "", + "§7Jacob's Tickets: §a" + GardenGuiSupport.personal(player).getJacobsTickets(), + "§6§lGOLD §7medals: §6" + GardenGuiSupport.getMedalCount(player, "gold"), + "§f§lSILVER §7medals: §f" + GardenGuiSupport.getMedalCount(player, "silver"), + "§c§lBRONZE §7medals: §c" + GardenGuiSupport.getMedalCount(player, "bronze"), + "", + "§7Personal best crops: §e" + GardenGuiSupport.personal(player).getContestPersonalBests().size(), + "§7Claimed rewards tracked: §e" + GardenGuiSupport.personal(player).getClaimedContestRewards().size() + )); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIManageChips.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIManageChips.java new file mode 100644 index 0000000000..b8c11417e5 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIManageChips.java @@ -0,0 +1,202 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.click.Click; +import net.swofty.commons.StringUtility; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GUIManageChips extends StatelessView { + private static final int[] CHIP_SLOTS = {20, 21, 22, 23, 24, 29, 30, 31, 32, 33}; + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Manage Chips", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + layout.slot(4, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + return ItemStackCreator.getStackHead( + "§aManage Chips", + "2a4a77840449437d21f9d5f047f518413cb2f69e3ecbfb99386649c997ca1e91", + 1, + "§7Upgrade your §9Chips §7to gain powerful", + "§7buffs!", + "", + "§2✿ Sowdust", + "§7Sowdust is dropped from harvesting", + "§7crops in §aThe Garden §7and is used to", + "§7upgrade the Chips you've unlocked!", + "", + "§7Sowdust: §2" + StringUtility.commaify(GardenGuiSupport.personal(player).getSowdust()) + ); + }); + + List> chips = GardenServices.chips().getChips(); + for (int index = 0; index < Math.min(chips.size(), CHIP_SLOTS.length); index++) { + Map chip = chips.get(index); + int slot = CHIP_SLOTS[index]; + layout.slot(slot, (s, c) -> buildChip((SkyBlockPlayer) c.player(), chip), (click, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + String chipId = GardenConfigRegistry.getString(chip, "id", ""); + GardenData.GardenChipProgress progress = GardenGuiSupport.getOrCreateChipProgress(player, chipId); + + if (click.click() instanceof Click.RightShift || click.click() instanceof Click.LeftShift) { + int redeemed = redeemChip(player, chipId, Integer.MAX_VALUE); + if (redeemed > 0) { + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cYou don't have any of that Chip to redeem."); + } + return; + } + + if (click.click() instanceof Click.Right) { + int redeemed = redeemChip(player, chipId, 1); + if (redeemed > 0) { + c.session(Object.class).refresh(); + } else { + player.sendMessage("§cYou don't have any of that Chip to redeem."); + } + return; + } + + int maxLevel = GardenServices.chips().getMaxLevel(progress.getRarity()); + if (progress.getLevel() <= 0 || maxLevel <= 0) { + player.sendMessage("§cRedeem a Chip first."); + return; + } + if (progress.getLevel() >= maxLevel) { + player.sendMessage("§eThat Chip is already at its current rarity cap."); + return; + } + + long cost = GardenServices.chips().getLevelCost(progress.getLevel() + 1); + if (GardenGuiSupport.personal(player).getSowdust() < cost) { + player.sendMessage("§cYou don't have enough Sowdust."); + return; + } + + GardenGuiSupport.personal(player).setSowdust(GardenGuiSupport.personal(player).getSowdust() - cost); + progress.setLevel(progress.getLevel() + 1); + player.playSuccessSound(); + c.session(Object.class).refresh(); + }); + } + + layout.slot(50, ItemStackCreator.getStack( + "§aChip Information", + net.minestom.server.item.Material.REDSTONE_TORCH, + 1, + "§7Chips can be upgraded using §2Sowdust§7.", + "§7To increase a Chip's §9maximum level§7,", + "§7redeem more copies of the same Chip.", + "", + "§7• §9§lRARE §7Chips max at §aLevel 10", + "§7• §5§lEPIC §7Chips max at §aLevel 15", + "§7• §6§lLEGENDARY §7Chips max at §aLevel 20" + )); + } + + private net.minestom.server.item.ItemStack.Builder buildChip(SkyBlockPlayer player, Map chip) { + String chipId = GardenConfigRegistry.getString(chip, "id", ""); + String displayName = GardenConfigRegistry.getString(chip, "display_name", StringUtility.toNormalCase(chipId)); + GardenData.GardenChipProgress progress = GardenGuiSupport.getOrCreateChipProgress(player, chipId); + String rarity = GardenServices.chips().resolveRarity(progress.getConsumed()); + progress.setRarity(rarity); + if (progress.getConsumed() > 0 && progress.getLevel() == 0) { + progress.setLevel(1); + } + + int maxLevel = GardenServices.chips().getMaxLevel(rarity); + Map effects = GardenConfigRegistry.getSection(chip, "effects"); + double perLevel = switch (rarity) { + case "LEGENDARY" -> GardenConfigRegistry.getDouble(effects, "legendary_per_level", 0D); + case "EPIC" -> GardenConfigRegistry.getDouble(effects, "epic_per_level", 0D); + case "RARE" -> GardenConfigRegistry.getDouble(effects, "rare_per_level", 0D); + default -> 0D; + }; + double currentValue = perLevel * progress.getLevel(); + double nextValue = perLevel * Math.min(maxLevel, progress.getLevel() + 1); + long nextCost = progress.getLevel() >= maxLevel ? 0L : GardenServices.chips().getLevelCost(progress.getLevel() + 1); + ItemType itemType = ItemType.get(chipId); + int availableToRedeem = itemType == null ? 0 : player.countItem(itemType); + + List lore = new ArrayList<>(List.of( + "§7Level " + progress.getLevel() + "§8/" + maxLevel, + "", + "§7Acquisition: §b" + GardenConfigRegistry.getString(chip, "acquisition", "Unknown"), + "", + "§6Ability", + "§7Current bonus: §a" + StringUtility.decimalify(currentValue, 2), + "" + )); + if (progress.getLevel() > 0 && progress.getLevel() < maxLevel) { + lore.add("§a§l=====[ UPGRADE ]====="); + lore.add("§7Next bonus: §a" + StringUtility.decimalify(nextValue, 2)); + lore.add(""); + lore.add("§7Cost"); + lore.add("§2" + StringUtility.commaify(nextCost) + " Sowdust"); + lore.add(""); + lore.add("§eClick to level up!"); + } else if (progress.getLevel() >= maxLevel) { + lore.add("§a§lMAXED FOR CURRENT RARITY"); + } else { + lore.add("§c§lLOCKED"); + } + + lore.add(""); + lore.add("§7Redeemable copies: §e" + availableToRedeem); + lore.add("§eRight-Click to redeem!"); + lore.add("§eShift-Click to redeem all!"); + lore.add(""); + lore.add(GardenGuiSupport.colorForRarity(rarity) + "§l" + rarity + " GARDEN CHIP"); + + String color = GardenGuiSupport.colorForRarity(rarity); + return GardenGuiSupport.itemWithLore( + chipId, + color + displayName, + lore + ); + } + + private int redeemChip(SkyBlockPlayer player, String chipId, int maxToRedeem) { + ItemType itemType = ItemType.get(chipId); + if (itemType == null) { + return 0; + } + int available = player.countItem(itemType); + int toRedeem = Math.min(available, maxToRedeem); + if (toRedeem <= 0 || player.takeItem(itemType, toRedeem) == null) { + return 0; + } + + GardenData.GardenChipProgress progress = GardenGuiSupport.getOrCreateChipProgress(player, chipId); + progress.setConsumed(progress.getConsumed() + toRedeem); + progress.setRarity(GardenServices.chips().resolveRarity(progress.getConsumed())); + if (progress.getLevel() == 0) { + progress.setLevel(1); + } + player.playSuccessSound(); + return toRedeem; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIPesthunter.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIPesthunter.java new file mode 100644 index 0000000000..accc930043 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIPesthunter.java @@ -0,0 +1,65 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class GUIPesthunter extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Pesthunter", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 31, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenPestsData pests = GardenGuiSupport.pests(player); + int activePests = pests.getActivePests().size(); + int penalty = net.swofty.type.garden.GardenServices.pests().getFortunePenaltyPercent(activePests, 0); + + layout.slot(11, ItemStackCreator.getStack( + "§aPest Storage", + Material.HOPPER_MINECART, + 1, + "§7Stored pests: §2" + pests.getStoredPests(), + "§7Active pests: §2" + activePests, + "", + "§7Offline pest timer: §e" + pests.getOfflineAccumulatorMs() / 1000 + "s" + )); + + layout.slot(13, ItemStackCreator.getStack( + "§ePesthunter Bonus", + Material.CLOCK, + 1, + "§7Current infestation penalty:", + penalty == 0 ? "§aNo Farming Fortune penalty" : "§c-" + penalty + "% Farming Fortune", + "", + pests.getRepellentEndsAt() > System.currentTimeMillis() + ? "§7Repellent active" + : "§7Repellent inactive" + )); + + layout.slot(15, ItemStackCreator.getStack( + "§4§lൠ §cGarden Infestation", + Material.RED_DYE, + 1, + "§7For every §2+100ൠ Bonus Pest Chance", + "§7you have, you can spawn §a1 §7extra pest", + "§7before the penalty begins.", + "", + "§7Current pests: §2" + activePests, + "§7Penalty: " + (penalty == 0 ? "§aSAFE" : "§c-" + penalty + "% ☘") + )); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMart.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMart.java new file mode 100644 index 0000000000..fa196df509 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMart.java @@ -0,0 +1,75 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class GUISkyMart extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("SkyMart", InventoryType.CHEST_4_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.close(layout, 31); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + + layout.slot(11, ItemStackCreator.getStack( + "§aFarming Essentials", + Material.GREEN_DYE, + 1, + "§7All the farming supplies you could", + "§7ever need!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUISkyMartCategory("farming_essentials", "Farming Essentials"))); + + layout.slot(12, ItemStackCreator.getStack( + "§aFarming Tools", + Material.DIAMOND_HOE, + 1, + "§7Purchase tools made specifically for", + "§7each crop!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUISkyMartCategory("farming_tools", "Farming Tools"))); + + layout.slot(13, ItemStackCreator.getStack( + "§aBarn Skins", + Material.OAK_PLANKS, + 1, + "§7Spruce up your Barn!", + "", + "§eClick to view!" + ), (click, c) -> c.push(new GUISkyMartCategory("barn_skins", "Barn Skins"))); + + layout.slot(14, ItemStackCreator.getStack( + "§aGreenhouse Skins", + Material.WHITE_STAINED_GLASS, + 1, + "§7Make your Greenhouse look fresh!", + "", + "§eClick to view!" + )); + + layout.slot(15, ItemStackCreator.getStack( + "§aPests", + Material.CHEST_MINECART, + 1, + "§7Got pests? We got you.", + "", + "§eClick to view!" + )); + + Components.back(layout, 30, ctx); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMartCategory.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMartCategory.java new file mode 100644 index 0000000000..dc8daba4ad --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUISkyMartCategory.java @@ -0,0 +1,117 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.swofty.commons.StringUtility; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GUISkyMartCategory extends StatelessView { + private static final int[] ENTRY_SLOTS = { + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24, 25, + 28, 29, 30, 31, 32, 33, 34 + }; + + private final String category; + private final String title; + + public GUISkyMartCategory(String category, String title) { + this.category = category; + this.title = title; + } + + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>(title, InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + List> entries = net.swofty.type.garden.GardenServices.desk().getSkyMartEntries(category); + for (int index = 0; index < Math.min(entries.size(), ENTRY_SLOTS.length); index++) { + Map entry = entries.get(index); + int slot = ENTRY_SLOTS[index]; + layout.slot(slot, (s, c) -> buildEntry((SkyBlockPlayer) c.player(), entry), (click, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + String id = GardenConfigRegistry.getString(entry, "id", ""); + long copper = GardenGuiSupport.core(player).getCopper(); + long price = GardenConfigRegistry.getLong(entry, "copper", 0L); + boolean repeatable = isRepeatable(id); + if (!repeatable && GardenGuiSupport.core(player).getSkyMartPurchases().contains(id)) { + player.sendMessage("§eYou already bought that SkyMart unlock."); + return; + } + if (copper < price) { + player.sendMessage("§cYou don't have enough Copper."); + return; + } + + ItemType itemType = ItemType.get(id); + if (id.endsWith("_BARN_SKIN")) { + GardenGuiSupport.core(player).setCopper(copper - price); + GardenGuiSupport.core(player).getSkyMartPurchases().add(id); + player.playSuccessSound(); + player.sendMessage("§aPurchased " + GardenConfigRegistry.getString(entry, "display_name", id) + "§a."); + c.session(Object.class).refresh(); + return; + } + if (itemType == null) { + player.sendMessage("§cThat item's runtime definition is not available yet."); + return; + } + + GardenGuiSupport.core(player).setCopper(copper - price); + player.addAndUpdateItem(itemType); + if (!repeatable) { + GardenGuiSupport.core(player).getSkyMartPurchases().add(id); + } + player.playSuccessSound(); + c.session(Object.class).refresh(); + }); + } + } + + private net.minestom.server.item.ItemStack.Builder buildEntry(SkyBlockPlayer player, Map entry) { + String id = GardenConfigRegistry.getString(entry, "id", ""); + String name = GardenConfigRegistry.getString(entry, "display_name", StringUtility.toNormalCase(id)); + long price = GardenConfigRegistry.getLong(entry, "copper", 0L); + boolean repeatable = isRepeatable(id); + boolean owned = GardenGuiSupport.core(player).getSkyMartPurchases().contains(id); + boolean available = ItemType.get(id) != null || id.endsWith("_BARN_SKIN"); + + List lore = new ArrayList<>(List.of( + "§7Cost", + "§c" + StringUtility.commaify(price) + " Copper", + "" + )); + if (!available) { + lore.add("§8Runtime item data pending"); + lore.add(""); + lore.add("§cUnavailable"); + } else if (!repeatable && owned) { + lore.add("§aPURCHASED"); + } else { + lore.add("§eClick to trade!"); + } + + return GardenGuiSupport.itemWithLore(id, "§a" + name, lore); + } + + private boolean isRepeatable(String id) { + return id.endsWith("_GARDEN_CHIP"); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorLogbook.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorLogbook.java new file mode 100644 index 0000000000..788bb9b297 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorLogbook.java @@ -0,0 +1,203 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.visitor.GardenVisitorService; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.PaginatedView; +import net.swofty.type.generic.gui.v2.StatefulPaginatedView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ClickContext; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class GUIVisitorLogbook extends StatefulPaginatedView, GUIVisitorLogbook.State> { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Visitor's Logbook", InventoryType.CHEST_6_ROW); + } + + @Override + public State initialState() { + List> entries = new ArrayList<>(GardenServices.visitors().getVisitors()); + entries.sort(Comparator.comparing(entry -> GardenConfigRegistry.getString(entry, "display_name", ""))); + return new State(entries, 0); + } + + @Override + protected int[] getPaginatedSlots() { + return DEFAULT_SLOTS; + } + + @Override + protected ItemStack.Builder renderItem(Map visitor, int index, HypixelPlayer hypixelPlayer) { + SkyBlockPlayer player = (SkyBlockPlayer) hypixelPlayer; + String id = GardenConfigRegistry.getString(visitor, "id", ""); + String displayName = GardenConfigRegistry.getString(visitor, "display_name", StringUtility.toNormalCase(id)); + String rarity = GardenConfigRegistry.getString(visitor, "rarity", "UNCOMMON"); + int visitCount = GardenGuiSupport.visitors(player).getVisitCounts().getOrDefault(id, 0); + int servedCount = GardenGuiSupport.visitors(player).getServedCounts().getOrDefault(id, 0); + boolean unlocked = isUnlockedForDisplay(player, visitor); + boolean active = isCurrentlyPresent(player, id); + + List lore = new ArrayList<>(); + lore.add(GardenGuiSupport.colorForRarity(rarity) + "§l" + rarity); + lore.add(""); + + if (!unlocked && visitCount == 0) { + lore.add("§7Requirement:"); + for (GardenVisitorService.VisitorRequirement requirement : GardenServices.visitors().getRequirements(visitor)) { + lore.add("§c✖ " + GardenServices.visitors().renderRequirement(requirement)); + } + if (GardenServices.visitors().getRequirements(visitor).isEmpty()) { + lore.add("§c✖ Requirement data unavailable"); + } + return ItemStackCreator.getStack("§7" + displayName, Material.GRAY_DYE, 1, lore); + } + + lore.add("§7Times Visited: §a" + visitCount); + lore.add("§7Offers Accepted: §a" + servedCount); + lore.add("§7Currently: " + (active ? "§aAt your Desk" : "§7Not visiting")); + lore.add(""); + + List wantedItems = GardenConfigRegistry.getList(visitor, "wanted_items"); + if (!wantedItems.isEmpty()) { + lore.add("§7Wanted Items:"); + wantedItems.stream().limit(3).forEach(item -> lore.add(" §8- §7" + StringUtility.toNormalCase(String.valueOf(item)))); + if (wantedItems.size() > 3) { + lore.add(" §8- §7+" + (wantedItems.size() - 3) + " more"); + } + lore.add(""); + } + + lore.add(active ? "§eClick to view offer!" : "§7This visitor is not currently on your Garden."); + return GardenGuiSupport.visitorIcon(visitor, displayName, rarity, lore); + } + + @Override + protected void onItemClick(ClickContext click, ViewContext ctx, Map visitor, int index) { + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + String id = GardenConfigRegistry.getString(visitor, "id", ""); + if (isCurrentlyPresent(player, id)) { + ctx.push(new GardenVisitorOfferView(id)); + } + } + + @Override + protected boolean shouldFilterFromSearch(State state, Map item) { + return false; + } + + @Override + protected void layoutCustom(ViewLayout layout, State state, ViewContext ctx) { + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenVisitorsData visitors = GardenGuiSupport.visitors(player); + long now = System.currentTimeMillis(); + String nextVisitor = visitors.getActiveVisitors().size() + visitors.getQueuedVisitors().size() + >= GardenServices.visitors().getMaxVisibleVisitors() + GardenServices.visitors().getMaxQueuedVisitors() + && now >= visitors.getNextArrivalAt() + ? "§c§lQueue Full!" + : "§a" + StringUtility.formatTimeLeft(Math.max(0L, visitors.getNextArrivalAt() - now)); + + layout.slot(4, ItemStackCreator.getStackHead( + "§aLogbook", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7Various NPCs will visit your island", + "§7and queue at your barn stand to", + "§7make offers.", + "", + "§7Harvesting crops will reduce the time", + "§7until the next visitor appears.", + "", + "§7Next Visitor: " + nextVisitor, + "", + "§7New Visitors come every §a" + GardenServices.visitors().getArrivalMinutes(GardenGuiSupport.core(player).getServedUniqueVisitors().size()) + " minutes§7.", + "§7Known entries: §e" + visitors.getLogbookEntries().size() + "§7/§a" + GardenServices.visitors().getExpectedUniqueVisitors() + )); + + if (!Components.back(layout, 48, ctx)) { + layout.slot(48, FILLER); + } + Components.close(layout, 49); + layout.slot(50, ItemStackCreator.getStack( + "§aVisitor Milestones", + Material.GOLD_BLOCK, + 1, + "§7Review your unique visitor and", + "§7offer-accept milestone progress.", + "", + "§eClick to view!" + ), (click, viewCtx) -> viewCtx.push(new GUIVisitorMilestones())); + } + + @Override + protected int getPreviousPageSlot() { + return 45; + } + + @Override + protected int getNextPageSlot() { + return 53; + } + + private boolean isUnlockedForDisplay(SkyBlockPlayer player, Map visitor) { + if (GardenGuiSupport.core(player).getLevel() < GardenConfigRegistry.getInt(visitor, "garden_level", 1)) { + return false; + } + for (GardenVisitorService.VisitorRequirement requirement : GardenServices.visitors().getRequirements(visitor)) { + switch (requirement.type()) { + case "SPOKEN_TO_NPC" -> { + String flag = requirement.key().toLowerCase().replaceAll("[^a-z0-9]+", "_").replaceAll("^_+|_+$", ""); + if (!GardenGuiSupport.personal(player).getSpokenNpcFlags().contains(flag) + && List.of("sam", "anita", "pamela", "jacob", "jeff", "phillip", "carpenter", "shifty", "desk").contains(flag)) { + return false; + } + } + case "GARDEN_LEVEL_AT_LEAST", "SKILL_LEVEL_AT_LEAST" -> { + } + default -> { + if (!GardenGuiSupport.personal(player).getVisitorRequirementFlags().contains(requirement.key().toLowerCase()) + && !GardenGuiSupport.core(player).getVisitorRequirementFlags().contains(requirement.key().toLowerCase()) + && !GardenGuiSupport.personal(player).getAccessFlags().contains(requirement.key().toLowerCase())) { + return false; + } + } + } + } + return true; + } + + private boolean isCurrentlyPresent(SkyBlockPlayer player, String visitorId) { + return GardenGuiSupport.visitors(player).getActiveVisitors().stream() + .anyMatch(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)) + || GardenGuiSupport.visitors(player).getQueuedVisitors().stream() + .anyMatch(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)); + } + + public record State(List> items, + int page) implements PaginatedView.PaginatedState> { + @Override + public PaginatedView.PaginatedState> withPage(int page) { + return new State(items, page); + } + + @Override + public PaginatedView.PaginatedState> withItems(List> items) { + return new State(items, page); + } + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorMilestones.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorMilestones.java new file mode 100644 index 0000000000..e9eba16f64 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GUIVisitorMilestones.java @@ -0,0 +1,94 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.milestone.GardenMilestoneService; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIVisitorMilestones extends StatelessView { + @Override + public ViewConfiguration configuration() { + return new ViewConfiguration<>("Visitor Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + List tracks = GardenServices.milestones().getVisitorTracks(); + int completedTiers = GardenGuiSupport.core(player).getVisitorMilestones().values().stream() + .mapToInt(Integer::intValue) + .sum(); + + layout.slot(4, ItemStackCreator.getStackHead( + "§aVisitor Milestones", + "8d34f38c1bb106e11908ad3cc90162c18b863d678265c84a84a358903f8f7a1c", + 1, + "§7Serve unique visitors and accept offers", + "§7to unlock visitor milestone rewards.", + "", + "§7Unique visitors served: §e" + GardenGuiSupport.core(player).getServedUniqueVisitors().size(), + "§7Offers accepted: §e" + GardenGuiSupport.visitors(player).getServedCounts().values().stream().mapToInt(Integer::intValue).sum(), + "§7Completed tiers: §e" + StringUtility.commaify(completedTiers) + )); + + int[] trackSlots = {20, 24}; + for (int index = 0; index < Math.min(tracks.size(), trackSlots.length); index++) { + GardenMilestoneService.VisitorMilestoneTrack track = tracks.get(index); + GardenMilestoneService.MilestoneProgress progress = GardenServices.milestones().getVisitorProgress(player, track.id()); + List lore = new ArrayList<>(); + lore.add("§7Tier: §e" + progress.completedTiers() + "§7/§a" + track.tiers().size()); + lore.add("§7Progress: §e" + StringUtility.commaify(progress.progress())); + lore.add(""); + + if (progress.nextTier() != null) { + long progressIntoTier = Math.max(0L, progress.progress() - progress.previousThreshold()); + long neededThisTier = Math.max(1L, progress.nextThreshold() - progress.previousThreshold()); + long remaining = Math.max(0L, progress.nextThreshold() - progress.progress()); + lore.add("§7Progress to Tier §a" + StringUtility.getAsRomanNumeral(progress.nextTier().tier()) + "§7:"); + lore.add(GardenGuiSupport.progressBar(progressIntoTier, neededThisTier)); + lore.add("§7Remaining: §e" + StringUtility.commaify(remaining)); + lore.add(""); + lore.add("§7Next Rewards:"); + lore.add(" §8+§3" + StringUtility.commaify(progress.nextTier().farmingXp()) + " Farming XP"); + lore.add(" §8+§2" + progress.nextTier().gardenXp() + " Garden XP"); + lore.add(" §8+§b" + progress.nextTier().skyblockXp() + " SkyBlock XP"); + } else { + lore.add("§aMAXED"); + lore.add(""); + lore.add("§7This milestone track has been"); + lore.add("§7completed in full."); + } + + layout.slot(trackSlots[index], GardenGuiSupport.itemWithLore( + track.iconItemId(), + "§a" + track.displayName(), + lore + )); + } + + layout.slot(31, ItemStackCreator.getStack( + "§aVisitor's Logbook", + Material.BOOK, + 1, + "§7Browse every Garden visitor and", + "§7review their visit history.", + "", + "§eClick to browse!" + ), (click, c) -> c.push(new GUIVisitorLogbook())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GardenGuiSupport.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GardenGuiSupport.java new file mode 100644 index 0000000000..037053440c --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GardenGuiSupport.java @@ -0,0 +1,428 @@ +package net.swofty.type.garden.gui; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.component.DataComponents; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.commons.ChatColor; +import net.swofty.commons.ServiceType; +import net.swofty.commons.StringUtility; +import net.swofty.commons.protocol.objects.jacobscontest.GetJacobContestScheduleProtocol; +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.proxyapi.ProxyService; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenBarnSkinDefinition; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.config.GardenPlotDefinition; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.garden.visitor.GardenBarnRuntime; +import net.swofty.type.generic.data.datapoints.DatapointInteger; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.skyblockgeneric.calendar.SkyBlockCalendar; +import net.swofty.type.skyblockgeneric.data.SkyBlockDataHandler; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenComposter; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenCore; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenGreenhouse; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenPersonal; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenPests; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenVisitors; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.item.SkyBlockItem; +import net.swofty.type.skyblockgeneric.skill.SkillCategories; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class GardenGuiSupport { + private static final ProxyService JACOBS_CONTEST_SERVICE = new ProxyService(ServiceType.JACOBS_CONTEST); + private static final Map FALLBACK_MATERIALS = Map.ofEntries( + Map.entry("DEFAULT", Material.DARK_OAK_PLANKS), + Map.entry("SPRUCE", Material.SPRUCE_LOG), + Map.entry("RED", Material.QUARTZ_BLOCK), + Map.entry("SUNNY", Material.RED_SANDSTONE), + Map.entry("CABIN", Material.LIGHT_BLUE_TERRACOTTA), + Map.entry("CHOCOLATE", Material.COCOA_BEANS), + Map.entry("WHEAT", Material.WHEAT), + Map.entry("CARROT", Material.CARROT), + Map.entry("POTATO", Material.POTATO), + Map.entry("PUMPKIN", Material.CARVED_PUMPKIN), + Map.entry("SUGAR_CANE", Material.SUGAR_CANE), + Map.entry("MELON_SLICE", Material.MELON_SLICE), + Map.entry("CACTUS", Material.CACTUS), + Map.entry("COCOA_BEANS", Material.COCOA_BEANS), + Map.entry("MUSHROOM", Material.RED_MUSHROOM), + Map.entry("NETHER_WART", Material.NETHER_WART), + Map.entry("SUNFLOWER", Material.SUNFLOWER), + Map.entry("MOONFLOWER", Material.BLUE_ORCHID), + Map.entry("WILD_ROSE", Material.ROSE_BUSH) + ); + + private GardenGuiSupport() { + } + + public static GardenData.GardenCoreData core(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_CORE, DatapointGardenCore.class) + .getValue(); + } + + public static GardenData.GardenVisitorsData visitors(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_VISITORS, DatapointGardenVisitors.class) + .getValue(); + } + + public static GardenData.GardenPestsData pests(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_PESTS, DatapointGardenPests.class) + .getValue(); + } + + public static GardenData.GardenComposterData composter(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_COMPOSTER, DatapointGardenComposter.class) + .getValue(); + } + + public static GardenData.GardenGreenhouseData greenhouse(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_GREENHOUSE, DatapointGardenGreenhouse.class) + .getValue(); + } + + public static GardenData.GardenPersonalData personal(SkyBlockPlayer player) { + return player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_PERSONAL, DatapointGardenPersonal.class) + .getValue(); + } + + public static SkyBlockGarden garden(SkyBlockPlayer player) { + return player.getSkyBlockGarden() instanceof SkyBlockGarden garden ? garden : null; + } + + public static ItemStack.Builder itemWithLore(String itemId, String displayName, int amount, List lore) { + ItemStack.Builder builder = baseItem(itemId, amount); + builder.set(DataComponents.CUSTOM_NAME, Component.text(displayName).decoration(TextDecoration.ITALIC, false)); + return ItemStackCreator.updateLore(builder, lore); + } + + public static ItemStack.Builder itemWithLore(String itemId, String displayName, List lore) { + return itemWithLore(itemId, displayName, 1, lore); + } + + public static String colorForRarity(String rarity) { + return switch (rarity == null ? "COMMON" : rarity.toUpperCase()) { + case "UNCOMMON" -> "§a"; + case "RARE" -> "§9"; + case "EPIC" -> "§5"; + case "LEGENDARY" -> "§6"; + case "MYTHIC" -> "§d"; + case "SPECIAL" -> "§c"; + default -> "§f"; + }; + } + + public static String progressBar(double current, double max) { + if (max <= 0D) { + return "§8Progress threshold unavailable"; + } + return StringUtility.createLineProgressBar(20, ChatColor.DARK_GREEN, current, max); + } + + public static GardenData.GardenChipProgress getOrCreateChipProgress(SkyBlockPlayer player, String chipId) { + return personal(player).getChips().computeIfAbsent(chipId, ignored -> new GardenData.GardenChipProgress()); + } + + public static boolean isBarnSkinUnlocked(SkyBlockPlayer player, GardenBarnSkinDefinition definition) { + GardenData.GardenCoreData core = core(player); + if (core.getOwnedBarnSkins().contains(definition.id())) { + return true; + } + return GardenServices.levels().getLevels().stream().anyMatch(level -> + GardenConfigRegistry.getInt(level, "level", 0) <= core.getLevel() + && GardenConfigRegistry.getList(level, "barn_skin_unlocks").stream() + .map(String::valueOf) + .anyMatch(definition.id()::equalsIgnoreCase)); + } + + public static void syncComposter(SkyBlockPlayer player) { + GardenData.GardenComposterData data = composter(player); + long now = System.currentTimeMillis(); + if (data.getLastUpdatedAt() <= 0L) { + data.setLastUpdatedAt(now); + return; + } + + int speedTier = data.getUpgrades().getOrDefault("speed", 0); + int costReductionTier = data.getUpgrades().getOrDefault("cost_reduction", 0); + long cycleMillis = Math.max(1L, Math.round( + (GardenServices.composter().getBaseProductionSeconds() * 1000D) + / GardenServices.composter().calculateSpeedMultiplier(speedTier) + )); + double matterCost = GardenServices.composter().calculateOrganicMatterCost(costReductionTier); + double fuelCost = GardenServices.composter().calculateFuelCost(costReductionTier); + + long remaining = now - data.getLastUpdatedAt(); + while (remaining >= cycleMillis + && data.getOrganicMatter() >= matterCost + && data.getFuel() >= fuelCost) { + data.setOrganicMatter(data.getOrganicMatter() - matterCost); + data.setFuel(data.getFuel() - fuelCost); + data.setCompostAvailable(data.getCompostAvailable() + 1); + remaining -= cycleMillis; + } + data.setLastUpdatedAt(now - remaining); + } + + public static boolean acceptVisitor(SkyBlockPlayer player, String visitorId) { + GardenData.GardenVisitorsData visitors = visitors(player); + Optional visitorOptional = visitors.getActiveVisitors().stream() + .filter(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)) + .findFirst(); + if (visitorOptional.isEmpty()) { + return false; + } + + GardenData.GardenVisitorState visitor = visitorOptional.get(); + Map requiredItems = new LinkedHashMap<>(); + for (GardenData.GardenRequest request : visitor.getRequests()) { + ItemType type = ItemType.get(request.getItemId()); + if (type == null) { + return false; + } + requiredItems.merge(type, request.getAmount(), Integer::sum); + } + + if (!player.removeItemsFromPlayer(requiredItems)) { + return false; + } + + visitors.getActiveVisitors().remove(visitor); + pushQueuedVisitor(visitors); + visitors.getServedCounts().merge(visitorId, 1, Integer::sum); + visitors.getLogbookEntries().add(visitorId); + + GardenData.GardenCoreData core = core(player); + boolean firstUniqueVisit = core.getServedUniqueVisitors().add(visitorId); + core.setExperience(core.getExperience() + visitor.getGardenXp()); + core.setCopper(core.getCopper() + visitor.getCopper()); + + player.getSkills().increase(player, SkillCategories.FARMING, (double) visitor.getFarmingXp()); + DatapointInteger bits = player.getSkyblockDataHandler().get(SkyBlockDataHandler.Data.BITS, DatapointInteger.class); + bits.setValue(bits.getValue() + visitor.getBits()); + + for (GardenData.GardenRewardState reward : visitor.getGuaranteedRewards()) { + giveReward(player, reward); + } + for (GardenData.GardenRewardState reward : visitor.getBonusRewards()) { + giveReward(player, reward); + } + + if (visitorId.equalsIgnoreCase("carpenter") && !greenhouse(player).isBlueprintUnlocked()) { + greenhouse(player).setBlueprintUnlocked(true); + giveReward(player, GardenServices.visitors().parseReward("GREENHOUSE_BLUEPRINT")); + } + + if (firstUniqueVisit) { + net.swofty.type.garden.GardenServices.milestones().advanceVisitorMilestone(player, "UNIQUE_SERVED", 1); + } + net.swofty.type.garden.GardenServices.milestones().advanceVisitorMilestone(player, "OFFERS_ACCEPTED", 1); + + player.playSuccessSound(); + GardenBarnRuntime.requestImmediateSync(player); + return true; + } + + public static boolean refuseVisitor(SkyBlockPlayer player, String visitorId) { + GardenData.GardenVisitorsData visitors = visitors(player); + boolean removed = visitors.getActiveVisitors().removeIf(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)); + if (removed) { + pushQueuedVisitor(visitors); + GardenBarnRuntime.requestImmediateSync(player); + } + return removed; + } + + public static List getUpcomingContests(int amount) { + if (!JACOBS_CONTEST_SERVICE.isOnline().join()) { + return List.of(); + } + + Object response = JACOBS_CONTEST_SERVICE.handleRequest( + new GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage( + SkyBlockCalendar.getElapsed(), + amount + ) + ).join(); + + if (!(response instanceof GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse contests)) { + return List.of(); + } + + int year = contests.year(); + long previousStart = Long.MIN_VALUE; + List displayEntries = new ArrayList<>(); + for (GetJacobContestScheduleProtocol.ContestScheduleEntry entry : contests.entries()) { + if (previousStart != Long.MIN_VALUE && entry.startTime() < previousStart) { + year++; + } + displayEntries.add(new UpcomingContestDisplay(year, entry)); + previousStart = entry.startTime(); + } + return displayEntries; + } + + public static String formatContestDate(UpcomingContestDisplay display) { + return SkyBlockCalendar.getMonthName(display.entry().startTime()) + " " + + StringUtility.commaifyAndTh(SkyBlockCalendar.getDay(display.entry().startTime())); + } + + public static String formatContestCrops(GetJacobContestScheduleProtocol.ContestScheduleEntry entry, int activeIndex) { + List crops = new ArrayList<>(); + for (int index = 0; index < entry.crops().size(); index++) { + String prefix = entry.index() == activeIndex && index == entry.crops().size() - 1 ? "§6☘ " : "§e○ "; + crops.add(prefix + "§7" + StringUtility.toNormalCase(entry.crops().get(index))); + } + return String.join("\n", crops); + } + + public static int getMedalCount(SkyBlockPlayer player, String key) { + return personal(player).getMedals().getOrDefault(key.toUpperCase(), 0); + } + + public static List lockedPlotsFirst(SkyBlockPlayer player) { + GardenData.GardenCoreData core = core(player); + SkyBlockGarden garden = garden(player); + if (garden == null) { + return List.of(); + } + return garden.getPlotService().getPlots().stream() + .sorted(Comparator + .comparing((GardenPlotDefinition plot) -> plot.defaultUnlocked() || core.getUnlockedPlots().contains(plot.id())) + .thenComparing(GardenPlotDefinition::id)) + .toList(); + } + + private static ItemStack.Builder baseItem(String itemId, int amount) { + ItemType itemType = ItemType.get(itemId); + if (itemType != null) { + ItemStack.Builder builder = new SkyBlockItem(itemType).getDisplayItem().amount(amount); + return builder; + } + return ItemStack.builder(FALLBACK_MATERIALS.getOrDefault(itemId.toUpperCase(), Material.BOOK)).amount(amount); + } + + private static void pushQueuedVisitor(GardenData.GardenVisitorsData visitors) { + if (visitors.getActiveVisitors().size() >= 5 || visitors.getQueuedVisitors().isEmpty()) { + return; + } + GardenData.GardenVisitorState queued = visitors.getQueuedVisitors().removeFirst(); + queued.setQueued(false); + visitors.getActiveVisitors().add(queued); + } + + public static String describeReward(GardenData.GardenRewardState reward) { + return GardenServices.visitors().describeReward(reward); + } + + public static List describeRewards(List rewards, String colorPrefix) { + List lore = new ArrayList<>(); + for (GardenData.GardenRewardState reward : rewards) { + String description = describeReward(reward); + if (!description.isBlank()) { + lore.add(colorPrefix + description); + } + } + return lore; + } + + public static ItemStack.Builder visitorIcon(Map definition, String displayName, String rarity, List lore) { + String iconHeadTexture = GardenServices.visitors().getIconHeadTexture(definition); + if (!iconHeadTexture.isBlank()) { + return ItemStackCreator.getStackHead( + colorForRarity(rarity) + displayName, + iconHeadTexture, + 1, + lore.toArray(String[]::new) + ); + } + + String iconItem = GardenServices.visitors().getIconItem(definition); + if (!iconItem.isBlank()) { + return itemWithLore(iconItem, colorForRarity(rarity) + displayName, lore); + } + + if ("VILLAGER".equals(GardenServices.visitors().getEntityKind(definition))) { + return ItemStackCreator.getStack( + colorForRarity(rarity) + displayName, + Material.VILLAGER_SPAWN_EGG, + 1, + lore + ); + } + + return itemWithLore("BOOK", colorForRarity(rarity) + displayName, lore); + } + + private static void giveReward(SkyBlockPlayer player, GardenData.GardenRewardState reward) { + if (reward == null) { + return; + } + long amount = normalizeRewardAmount(reward); + switch (reward.getType()) { + case "ITEM" -> { + ItemType itemType = ItemType.get(reward.getKey()); + if (itemType != null) { + player.addAndUpdateItem(itemType, (int) Math.max(1L, amount)); + } + } + case "BITS" -> { + DatapointInteger bits = player.getSkyblockDataHandler().get(SkyBlockDataHandler.Data.BITS, DatapointInteger.class); + bits.setValue(bits.getValue() + (int) amount); + } + case "COPPER" -> core(player).setCopper(core(player).getCopper() + amount); + case "FARMING_XP" -> player.getSkills().increase(player, SkillCategories.FARMING, (double) amount); + case "GARDEN_XP" -> core(player).setExperience(core(player).getExperience() + amount); + case "JACOBS_TICKET" -> + personal(player).setJacobsTickets(personal(player).getJacobsTickets() + (int) amount); + case "BARN_SKIN_UNLOCK" -> core(player).getOwnedBarnSkins().add(reward.getKey().toLowerCase()); + case "GREENHOUSE_UNLOCK" -> greenhouse(player).setBlueprintUnlocked(true); + case "SKYMART_UNLOCK" -> core(player).getSkyMartPurchases().add(reward.getKey().toLowerCase()); + case "TUTORIAL_FLAG" -> personal(player).getTutorialFlags().add(reward.getKey().toLowerCase()); + case "PROFILE_FLAG", "ACCESS_FLAG" -> + personal(player).getVisitorRequirementFlags().add(reward.getKey().toLowerCase()); + case "PROFILE_COUNTER" -> + personal(player).getVisitorRequirementCounters().merge(reward.getKey().toLowerCase(), amount, Long::sum); + case "POWDER" -> + personal(player).getVisitorRequirementCounters().merge(("powder_" + reward.getKey()).toLowerCase(), amount, Long::sum); + case "ESSENCE" -> + personal(player).getVisitorRequirementCounters().merge(("essence_" + reward.getKey()).toLowerCase(), amount, Long::sum); + case "PELTS" -> personal(player).getVisitorRequirementCounters().merge("pelts", amount, Long::sum); + case "FAIRY_SOUL" -> + personal(player).getVisitorRequirementCounters().merge("fairy_souls", amount, Long::sum); + default -> { + } + } + } + + private static long normalizeRewardAmount(GardenData.GardenRewardState reward) { + if (reward.getAmount() > 0) { + return reward.getAmount(); + } + if (reward.getMin() > 0 && reward.getMax() >= reward.getMin()) { + return reward.getMax() == reward.getMin() + ? reward.getMin() + : java.util.concurrent.ThreadLocalRandom.current().nextLong(reward.getMin(), reward.getMax() + 1); + } + return 1L; + } + + public record UpcomingContestDisplay(int year, GetJacobContestScheduleProtocol.ContestScheduleEntry entry) { + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/gui/GardenVisitorOfferView.java b/type.garden/src/main/java/net/swofty/type/garden/gui/GardenVisitorOfferView.java new file mode 100644 index 0000000000..722d260497 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/gui/GardenVisitorOfferView.java @@ -0,0 +1,129 @@ +package net.swofty.type.garden.gui; + +import net.minestom.server.inventory.InventoryType; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.Components; +import net.swofty.type.generic.gui.v2.DefaultState; +import net.swofty.type.generic.gui.v2.StatelessView; +import net.swofty.type.generic.gui.v2.ViewConfiguration; +import net.swofty.type.generic.gui.v2.ViewLayout; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GardenVisitorOfferView extends StatelessView { + private final String visitorId; + + public GardenVisitorOfferView(String visitorId) { + this.visitorId = visitorId; + } + + @Override + public ViewConfiguration configuration() { + String displayName = GardenConfigRegistry.getString( + net.swofty.type.garden.GardenServices.visitors().getVisitor(visitorId), + "display_name", + StringUtility.toNormalCase(visitorId) + ); + return new ViewConfiguration<>(displayName, InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, DefaultState state, ViewContext ctx) { + Components.fill(layout); + Components.backOrClose(layout, 49, ctx); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + GardenData.GardenVisitorState active = GardenGuiSupport.visitors(player).getActiveVisitors().stream() + .filter(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)) + .findFirst() + .orElse(null); + Map definition = net.swofty.type.garden.GardenServices.visitors().getVisitor(visitorId); + String displayName = GardenConfigRegistry.getString(definition, "display_name", StringUtility.toNormalCase(visitorId)); + String rarity = GardenConfigRegistry.getString(definition, "rarity", active == null ? "UNCOMMON" : active.getRarity()); + + List profileLore = new ArrayList<>(List.of( + GardenGuiSupport.colorForRarity(rarity) + "§l" + rarity, + "", + "§7Times Visited: §a" + GardenGuiSupport.visitors(player).getVisitCounts().getOrDefault(visitorId, 0), + "§7Offers Accepted: §a" + GardenGuiSupport.visitors(player).getServedCounts().getOrDefault(visitorId, 0) + )); + layout.slot(13, GardenGuiSupport.visitorIcon(definition, displayName, rarity, profileLore)); + + if (active == null) { + layout.slot(31, ItemStackCreator.getStack( + "§cNo Active Offer", + net.minestom.server.item.Material.BARRIER, + 1, + "§7This visitor is not currently on", + "§7your Garden." + )); + return; + } + + boolean canAccept = canAccept(player, active); + List acceptLore = new ArrayList<>(); + acceptLore.add("§7Items Required:"); + for (GardenData.GardenRequest request : active.getRequests()) { + acceptLore.add(" §7" + StringUtility.toNormalCase(request.getItemId()) + " §8x" + StringUtility.commaify(request.getAmount())); + } + acceptLore.add(""); + acceptLore.add("§7Rewards:"); + acceptLore.add(" §8+§3" + StringUtility.commaify(active.getFarmingXp()) + " §7Farming XP"); + acceptLore.add(" §8+§2" + active.getGardenXp() + " §7Garden Experience"); + acceptLore.add(" §8+§c" + active.getCopper() + " Copper"); + if (active.getBits() > 0) { + acceptLore.add(" §8+§b" + active.getBits() + " Bits"); + } + acceptLore.addAll(GardenGuiSupport.describeRewards(active.getGuaranteedRewards(), " §8+§6")); + acceptLore.addAll(GardenGuiSupport.describeRewards(active.getBonusRewards(), " §8+§d")); + acceptLore.add(""); + acceptLore.add(canAccept ? "§eClick to accept!" : "§cMissing items to accept!"); + + layout.slot(29, ItemStackCreator.getStack( + "§aAccept Offer", + net.minestom.server.item.Material.GREEN_TERRACOTTA, + 1, + acceptLore + ), (click, c) -> { + if (!GardenGuiSupport.acceptVisitor(player, visitorId)) { + player.sendMessage("§cYou don't have the required items."); + return; + } + player.closeInventory(); + }); + + layout.slot(33, ItemStackCreator.getStack( + "§cRefuse Offer", + net.minestom.server.item.Material.RED_TERRACOTTA, + 1, + "§7" + displayName + " §7will leave your §aGarden", + "§7and maybe come back later.", + "", + "§eClick to refuse!" + ), (click, c) -> { + GardenGuiSupport.refuseVisitor(player, visitorId); + player.closeInventory(); + }); + } + + private boolean canAccept(SkyBlockPlayer player, GardenData.GardenVisitorState visitor) { + for (GardenData.GardenRequest request : visitor.getRequests()) { + net.swofty.commons.skyblock.item.ItemType itemType = net.swofty.commons.skyblock.item.ItemType.get(request.getItemId()); + if (itemType == null) { + return false; + } + int available = player.getAmountInInventory(itemType) + player.getSackItems().getAmount(itemType); + if (available < request.getAmount()) { + return false; + } + } + return true; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/level/GardenLevelService.java b/type.garden/src/main/java/net/swofty/type/garden/level/GardenLevelService.java new file mode 100644 index 0000000000..01b5106b5f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/level/GardenLevelService.java @@ -0,0 +1,136 @@ +package net.swofty.type.garden.level; + +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GardenLevelService { + private Map config = Map.of(); + private List> levels = List.of(); + + public void reload() { + config = GardenConfigRegistry.getConfig("levels.yml"); + levels = GardenConfigRegistry.getMapList(config, "levels"); + } + + public int getMaxLevel() { + return GardenConfigRegistry.getInt(config, "max_level", 15); + } + + public int getCropGrowthPerLevelPercent() { + return GardenConfigRegistry.getInt(config, "crop_growth_per_level_percent", 10); + } + + public int getSkyBlockXpPerLevel() { + return GardenConfigRegistry.getInt(config, "skyblock_xp_per_level", 10); + } + + public int getUnlockedVisitorsForLevel(int level) { + return findLevel(level) + .map(entry -> GardenConfigRegistry.getInt(entry, "visitor_unlocks", 0)) + .orElse(0); + } + + public List getRewardsForLevel(int level) { + return findLevel(level) + .map(entry -> { + List legacySummary = GardenConfigRegistry.getList(entry, "reward_summary").stream() + .map(String::valueOf) + .toList(); + if (!legacySummary.isEmpty()) { + return legacySummary; + } + + List rendered = new ArrayList<>(); + for (Object rawReward : GardenConfigRegistry.getList(entry, "rewards")) { + rendered.add(renderReward(rawReward)); + } + return rendered; + }) + .orElse(List.of()); + } + + public List> getLevels() { + return new ArrayList<>(levels); + } + + public Map getLevel(int level) { + return findLevel(level).orElse(Map.of()); + } + + private java.util.Optional> findLevel(int level) { + return levels.stream() + .filter(entry -> GardenConfigRegistry.getInt(entry, "level", 0) == level) + .findFirst(); + } + + @SuppressWarnings("unchecked") + private String renderReward(Object rawReward) { + if (rawReward instanceof Map rewardMapRaw) { + Map rewardMap = (Map) rewardMapRaw; + String type = GardenConfigRegistry.getString(rewardMap, "type", "").toUpperCase(); + return switch (type) { + case "VISITOR_UNLOCKS" -> "+" + GardenConfigRegistry.getInt(rewardMap, "amount", 0) + " Visitor" + + (GardenConfigRegistry.getInt(rewardMap, "amount", 0) == 1 ? "" : "s"); + case "CROP_UNLOCK" -> normalizeDisplay(GardenConfigRegistry.getString(rewardMap, "key", "")) + " Crop"; + case "PEST_UNLOCK" -> normalizeDisplay(GardenConfigRegistry.getString(rewardMap, "key", "")) + " Pest"; + case "CROP_UPGRADE_TIER" -> + "Tier " + toRoman(GardenConfigRegistry.getInt(rewardMap, "amount", 0)) + " Crop Upgrades"; + case "BARN_SKIN_UNLOCK" -> + normalizeDisplay(GardenConfigRegistry.getString(rewardMap, "key", "")) + " Barn Skin"; + case "GREENHOUSE_UNLOCK" -> "Greenhouse Unlock"; + case "SKYBLOCK_XP" -> "+" + GardenConfigRegistry.getInt(rewardMap, "amount", 0) + " SkyBlock XP"; + case "CROP_GROWTH" -> "+" + GardenConfigRegistry.getInt(rewardMap, "amount", 0) + " Crop Growth"; + default -> { + GardenData.GardenRewardState rewardState = new GardenData.GardenRewardState(); + rewardState.setType(type); + rewardState.setKey(GardenConfigRegistry.getString(rewardMap, "key", "")); + rewardState.setAmount(GardenConfigRegistry.getLong(rewardMap, "amount", 0L)); + rewardState.setDisplayOverride(GardenConfigRegistry.getString(rewardMap, "display", "")); + yield GardenServices.visitors().describeReward(rewardState); + } + }; + } + return String.valueOf(rawReward); + } + + private String normalizeDisplay(String key) { + if (key == null || key.isBlank()) { + return ""; + } + String[] parts = key.toLowerCase().split("_"); + StringBuilder builder = new StringBuilder(); + for (String part : parts) { + if (part.isBlank()) { + continue; + } + if (builder.length() > 0) { + builder.append(' '); + } + builder.append(Character.toUpperCase(part.charAt(0))); + if (part.length() > 1) { + builder.append(part.substring(1)); + } + } + return builder.toString(); + } + + private String toRoman(int value) { + return switch (value) { + case 1 -> "I"; + case 2 -> "II"; + case 3 -> "III"; + case 4 -> "IV"; + case 5 -> "V"; + case 6 -> "VI"; + case 7 -> "VII"; + case 8 -> "VIII"; + case 9 -> "IX"; + default -> String.valueOf(value); + }; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/milestone/GardenMilestoneService.java b/type.garden/src/main/java/net/swofty/type/garden/milestone/GardenMilestoneService.java new file mode 100644 index 0000000000..672e5da323 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/milestone/GardenMilestoneService.java @@ -0,0 +1,301 @@ +package net.swofty.type.garden.milestone; + +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.skill.SkillCategories; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class GardenMilestoneService { + private Map config = Map.of(); + private final Map cropDefinitions = new LinkedHashMap<>(); + private final Map visitorTracks = new LinkedHashMap<>(); + + public void reload() { + config = GardenConfigRegistry.getConfig("milestones.yml"); + + List cropRewardTiers = parseTierList( + GardenConfigRegistry.getMapList(config, "crop_reward_tiers") + ); + + Map> cropGroups = new LinkedHashMap<>(); + GardenConfigRegistry.getSection(config, "crop_groups").forEach((groupId, rawValue) -> { + if (!(rawValue instanceof Map groupMapRaw)) { + return; + } + @SuppressWarnings("unchecked") + Map groupMap = (Map) groupMapRaw; + List thresholds = GardenConfigRegistry.getList(groupMap, "thresholds").stream() + .map(this::toLong) + .filter(Objects::nonNull) + .toList(); + if (!thresholds.isEmpty()) { + cropGroups.put(normalizeKey(groupId), thresholds); + } + }); + + cropDefinitions.clear(); + for (Map cropEntry : GardenConfigRegistry.getMapList(config, "crop_milestones")) { + String cropId = normalizeKey(GardenConfigRegistry.getString(cropEntry, "id", "")); + String groupId = normalizeKey(GardenConfigRegistry.getString(cropEntry, "group", "")); + List thresholds = cropGroups.get(groupId); + if (cropId.isBlank() || thresholds == null || thresholds.isEmpty()) { + continue; + } + + cropDefinitions.put(cropId, new CropMilestoneDefinition( + cropId, + GardenConfigRegistry.getString(cropEntry, "display_name", StringUtility.toNormalCase(cropId)), + GardenConfigRegistry.getString(cropEntry, "icon", cropId), + combineThresholdsWithRewards(thresholds, cropRewardTiers) + )); + } + + visitorTracks.clear(); + GardenConfigRegistry.getSection(config, "visitor_tracks").forEach((trackId, rawValue) -> { + if (!(rawValue instanceof Map trackMapRaw)) { + return; + } + @SuppressWarnings("unchecked") + Map trackMap = (Map) trackMapRaw; + List tiers = parseTierList(GardenConfigRegistry.getMapList(trackMap, "tiers")); + if (tiers.isEmpty()) { + return; + } + String normalizedTrack = normalizeKey(trackId); + visitorTracks.put(normalizedTrack, new VisitorMilestoneTrack( + normalizedTrack, + GardenConfigRegistry.getString(trackMap, "display_name", StringUtility.toNormalCase(normalizedTrack)), + GardenConfigRegistry.getString(trackMap, "icon", "BOOK"), + tiers + )); + }); + } + + public List getCropDefinitions() { + return new ArrayList<>(cropDefinitions.values()); + } + + public CropMilestoneDefinition getCropDefinition(String cropId) { + return cropDefinitions.get(normalizeKey(cropId)); + } + + public List getVisitorTracks() { + return new ArrayList<>(visitorTracks.values()); + } + + public VisitorMilestoneTrack getVisitorTrack(String trackId) { + return visitorTracks.get(normalizeKey(trackId)); + } + + public MilestoneAdvanceResult advanceCropMilestone(SkyBlockPlayer player, String cropId, long amount) { + CropMilestoneDefinition definition = getCropDefinition(cropId); + if (definition == null || amount <= 0L) { + return MilestoneAdvanceResult.NONE; + } + + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + long updatedProgress = core.getCropMilestoneProgress().getOrDefault(definition.id(), 0L) + amount; + core.getCropMilestoneProgress().put(definition.id(), updatedProgress); + + int currentTier = core.getCropMilestones().getOrDefault(definition.id(), 0); + List unlockedTiers = new ArrayList<>(); + while (currentTier < definition.tiers().size() + && updatedProgress >= definition.tiers().get(currentTier).cumulativeAmount()) { + MilestoneTier unlocked = definition.tiers().get(currentTier); + unlockedTiers.add(unlocked); + awardMilestone(player, unlocked, definition.displayName(), "Crop"); + currentTier++; + } + + core.getCropMilestones().put(definition.id(), currentTier); + return new MilestoneAdvanceResult(updatedProgress, currentTier, unlockedTiers); + } + + public MilestoneAdvanceResult advanceVisitorMilestone(SkyBlockPlayer player, String trackId, long amount) { + VisitorMilestoneTrack track = getVisitorTrack(trackId); + if (track == null || amount <= 0L) { + return MilestoneAdvanceResult.NONE; + } + + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + long fallbackProgress = switch (track.id()) { + case "UNIQUE_SERVED" -> core.getServedUniqueVisitors().size(); + case "OFFERS_ACCEPTED" -> GardenGuiSupport.visitors(player).getServedCounts().values().stream() + .mapToLong(Integer::longValue) + .sum(); + default -> 0L; + }; + long storedProgress = core.getVisitorMilestoneProgress().getOrDefault(track.id(), 0L); + long baselineProgress = Math.max(storedProgress, Math.max(0L, fallbackProgress - amount)); + long updatedProgress = baselineProgress + amount; + core.getVisitorMilestoneProgress().put(track.id(), updatedProgress); + + int currentTier = core.getVisitorMilestones().getOrDefault(track.id(), 0); + List unlockedTiers = new ArrayList<>(); + while (currentTier < track.tiers().size() + && updatedProgress >= track.tiers().get(currentTier).cumulativeAmount()) { + MilestoneTier unlocked = track.tiers().get(currentTier); + unlockedTiers.add(unlocked); + awardMilestone(player, unlocked, track.displayName(), "Visitor"); + currentTier++; + } + + core.getVisitorMilestones().put(track.id(), currentTier); + return new MilestoneAdvanceResult(updatedProgress, currentTier, unlockedTiers); + } + + public MilestoneProgress getCropProgress(SkyBlockPlayer player, String cropId) { + CropMilestoneDefinition definition = getCropDefinition(cropId); + if (definition == null) { + return MilestoneProgress.EMPTY; + } + + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + long progress = core.getCropMilestoneProgress().getOrDefault(definition.id(), 0L); + int completedTiers = core.getCropMilestones().getOrDefault(definition.id(), 0); + return buildProgress(progress, completedTiers, definition.tiers()); + } + + public MilestoneProgress getVisitorProgress(SkyBlockPlayer player, String trackId) { + VisitorMilestoneTrack track = getVisitorTrack(trackId); + if (track == null) { + return MilestoneProgress.EMPTY; + } + + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + long fallbackProgress = switch (track.id()) { + case "UNIQUE_SERVED" -> core.getServedUniqueVisitors().size(); + case "OFFERS_ACCEPTED" -> GardenGuiSupport.visitors(player).getServedCounts().values().stream() + .mapToLong(Integer::longValue) + .sum(); + default -> 0L; + }; + long progress = Math.max(core.getVisitorMilestoneProgress().getOrDefault(track.id(), 0L), fallbackProgress); + int completedTiers = core.getVisitorMilestones().getOrDefault(track.id(), 0); + return buildProgress(progress, completedTiers, track.tiers()); + } + + private void awardMilestone(SkyBlockPlayer player, MilestoneTier tier, String displayName, String category) { + GardenData.GardenCoreData core = GardenGuiSupport.core(player); + core.setExperience(core.getExperience() + tier.gardenXp()); + player.getSkills().increase(player, SkillCategories.FARMING, (double) tier.farmingXp()); + + player.sendMessage("§a" + category + " Milestone §e" + displayName + " §7reached Tier §6" + + StringUtility.getAsRomanNumeral(tier.tier()) + "§7!"); + player.sendMessage("§8+§3" + StringUtility.commaify(tier.farmingXp()) + " Farming XP §8| §2+" + + tier.gardenXp() + " Garden XP"); + player.playSuccessSound(); + } + + private List combineThresholdsWithRewards(List thresholds, List rewardTiers) { + List tiers = new ArrayList<>(); + long cumulative = 0L; + for (int index = 0; index < Math.min(thresholds.size(), rewardTiers.size()); index++) { + cumulative += thresholds.get(index); + MilestoneTier rewardTier = rewardTiers.get(index); + tiers.add(new MilestoneTier( + index + 1, + thresholds.get(index), + cumulative, + rewardTier.farmingXp(), + rewardTier.gardenXp(), + rewardTier.skyblockXp() + )); + } + return tiers; + } + + private List parseTierList(List> entries) { + List tiers = new ArrayList<>(); + long cumulative = 0L; + for (Map entry : entries) { + long amount = GardenConfigRegistry.getLong(entry, "amount", 0L); + if (amount <= 0L) { + continue; + } + cumulative += amount; + tiers.add(new MilestoneTier( + GardenConfigRegistry.getInt(entry, "tier", tiers.size() + 1), + amount, + cumulative, + GardenConfigRegistry.getLong(entry, "farming_xp", 0L), + GardenConfigRegistry.getInt(entry, "garden_xp", 0), + GardenConfigRegistry.getInt(entry, "skyblock_xp", 0) + )); + } + return tiers; + } + + private MilestoneProgress buildProgress(long progress, int completedTiers, List tiers) { + if (tiers.isEmpty()) { + return MilestoneProgress.EMPTY; + } + + int clampedCompletedTiers = Math.max(0, Math.min(completedTiers, tiers.size())); + long previousThreshold = clampedCompletedTiers <= 0 ? 0L : tiers.get(clampedCompletedTiers - 1).cumulativeAmount(); + MilestoneTier nextTier = clampedCompletedTiers >= tiers.size() ? null : tiers.get(clampedCompletedTiers); + MilestoneTier currentTier = clampedCompletedTiers <= 0 ? null : tiers.get(clampedCompletedTiers - 1); + return new MilestoneProgress( + progress, + clampedCompletedTiers, + previousThreshold, + nextTier == null ? previousThreshold : nextTier.cumulativeAmount(), + currentTier, + nextTier + ); + } + + private String normalizeKey(String key) { + if (key == null) { + return ""; + } + return key.trim().replace(' ', '_').toUpperCase(); + } + + private Long toLong(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value == null) { + return null; + } + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException ignored) { + return null; + } + } + + public record MilestoneTier(int tier, long amount, long cumulativeAmount, long farmingXp, int gardenXp, + int skyblockXp) { + } + + public record CropMilestoneDefinition(String id, String displayName, String iconItemId, List tiers) { + } + + public record VisitorMilestoneTrack(String id, String displayName, String iconItemId, List tiers) { + } + + public record MilestoneProgress( + long progress, + int completedTiers, + long previousThreshold, + long nextThreshold, + MilestoneTier currentTier, + MilestoneTier nextTier + ) { + public static final MilestoneProgress EMPTY = new MilestoneProgress(0L, 0, 0L, 0L, null, null); + } + + public record MilestoneAdvanceResult(long progress, int completedTiers, List unlockedTiers) { + public static final MilestoneAdvanceResult NONE = new MilestoneAdvanceResult(0L, 0, List.of()); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/AbstractGardenNpc.java b/type.garden/src/main/java/net/swofty/type/garden/npc/AbstractGardenNpc.java new file mode 100644 index 0000000000..d72751a3b9 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/AbstractGardenNpc.java @@ -0,0 +1,89 @@ +package net.swofty.type.garden.npc; + +import net.minestom.server.coordinate.Pos; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.generic.entity.npc.HypixelNPC; +import net.swofty.type.generic.entity.npc.configuration.HumanConfiguration; +import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSupport; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public abstract class AbstractGardenNpc extends HypixelNPC { + private final String npcId; + + protected AbstractGardenNpc(String npcId, String displayName, String texture, String signature) { + super(new HumanConfiguration() { + @Override + public String[] holograms(HypixelPlayer player) { + return new String[]{displayName, "§e§lCLICK"}; + } + + @Override + public Pos position(HypixelPlayer player) { + if (!(player instanceof SkyBlockPlayer skyBlockPlayer)) { + return GardenNpcSupport.hiddenPosition(); + } + return GardenNpcAnchorRegistry.getNpcAnchor(skyBlockPlayer, npcId).map(GardenNpcAnchorRegistry.NpcAnchor::position).orElse(GardenNpcSupport.hiddenPosition()); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public boolean visible(HypixelPlayer player) { + return player instanceof SkyBlockPlayer skyBlockPlayer && GardenNpcSupport.isVisibleOnGarden(skyBlockPlayer) && GardenNpcAnchorRegistry.getNpcAnchor(skyBlockPlayer, npcId).isPresent(); + } + + @Override + public String texture(HypixelPlayer player) { + return texture; + } + + @Override + public String signature(HypixelPlayer player) { + return signature; + } + + @Override + public net.minestom.server.instance.Instance instance(HypixelPlayer player) { + return GardenNpcSupport.instanceFor(player instanceof SkyBlockPlayer skyBlockPlayer ? skyBlockPlayer : null); + } + }); + this.npcId = npcId; + } + + protected boolean hasSpoken(SkyBlockPlayer player) { + return GardenGuiSupport.personal(player).getSpokenNpcFlags().contains(GardenProgressionSupport.normalizeSpokenKey(npcId)); + } + + protected void markSpoken(SkyBlockPlayer player) { + GardenProgressionSupport.apply(player, net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionReward.spokenNpc(npcId)); + } + + protected DialogueSet[] configuredDialogues() { + Map config = GardenConfigRegistry.getConfig("visitor_dialogue.yml"); + Map npcConfig = GardenConfigRegistry.getSection( + GardenConfigRegistry.getSection(config, "npcs"), + npcId + ); + List dialogueSets = new ArrayList<>(); + for (Map entry : GardenConfigRegistry.getMapList(npcConfig, "dialogue_sets")) { + List lines = GardenConfigRegistry.getList(entry, "lines"); + if (lines.isEmpty()) { + continue; + } + dialogueSets.add(DialogueSet.builder() + .key(GardenConfigRegistry.getString(entry, "key", "hello")) + .lines(lines.stream().map(String::valueOf).toArray(String[]::new)) + .build()); + } + return dialogueSets.toArray(DialogueSet[]::new); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcAnchorRegistry.java b/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcAnchorRegistry.java new file mode 100644 index 0000000000..1f401d25b2 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcAnchorRegistry.java @@ -0,0 +1,55 @@ +package net.swofty.type.garden.npc; + +import net.minestom.server.coordinate.Pos; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.Map; +import java.util.Optional; + +public final class GardenNpcAnchorRegistry { + private GardenNpcAnchorRegistry() { + } + + public static Optional getNpcAnchor(SkyBlockPlayer player, String npcId) { + return getAnchor(player, "npcs", npcId); + } + + public static Optional getInteractionAnchor(SkyBlockPlayer player, String anchorId) { + return getAnchor(player, "interactions", anchorId); + } + + public static Optional getVisitorSlotAnchor(SkyBlockPlayer player, int slot) { + return getAnchor(player, "visitor_slots", "slot_" + slot); + } + + private static Optional getAnchor(SkyBlockPlayer player, String sectionName, String anchorId) { + Map config = GardenConfigRegistry.getConfig("npc_anchors.yml"); + Map skins = GardenConfigRegistry.getSection(config, "skins"); + String selectedSkin = player == null ? "default" : net.swofty.type.garden.gui.GardenGuiSupport.core(player).getSelectedBarnSkin(); + + Map selectedSkinSection = GardenConfigRegistry.getSection(skins, selectedSkin); + Map selectedAnchors = GardenConfigRegistry.getSection(selectedSkinSection, sectionName); + Map anchor = GardenConfigRegistry.getSection(selectedAnchors, anchorId); + if (anchor.isEmpty()) { + Map defaultSkinSection = GardenConfigRegistry.getSection(skins, "default"); + Map defaultAnchors = GardenConfigRegistry.getSection(defaultSkinSection, sectionName); + anchor = GardenConfigRegistry.getSection(defaultAnchors, anchorId); + } + + if (anchor.isEmpty() || !GardenConfigRegistry.getBoolean(anchor, "enabled", true)) { + return Optional.empty(); + } + + return Optional.of(new NpcAnchor(new Pos( + GardenConfigRegistry.getDouble(anchor, "x", 0D), + GardenConfigRegistry.getDouble(anchor, "y", 0D), + GardenConfigRegistry.getDouble(anchor, "z", 0D), + (float) GardenConfigRegistry.getDouble(anchor, "yaw", 0D), + (float) GardenConfigRegistry.getDouble(anchor, "pitch", 0D) + ), GardenConfigRegistry.getDouble(anchor, "offsetY", 0D))); + } + + public record NpcAnchor(Pos position, Double offsetY) { + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcSupport.java b/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcSupport.java new file mode 100644 index 0000000000..d4e1883206 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/GardenNpcSupport.java @@ -0,0 +1,31 @@ +package net.swofty.type.garden.npc; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.instance.Instance; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public final class GardenNpcSupport { + public static final Pos HIDDEN_POSITION = new Pos(0, -1000, 0); + + private GardenNpcSupport() { + } + + public static boolean isVisibleOnGarden(SkyBlockPlayer player) { + return player != null && player.isOnGarden(); + } + + public static Pos hiddenPosition() { + return HIDDEN_POSITION; + } + + public static Instance instanceFor(SkyBlockPlayer player) { + if (player != null + && player.getSkyBlockGarden() instanceof SkyBlockGarden garden + && garden.getGardenInstance() != null) { + return garden.getGardenInstance(); + } + return HypixelConst.getInstanceContainer(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCAnita.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCAnita.java new file mode 100644 index 0000000000..02fdd94e1d --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCAnita.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.garden.gui.GUIAnita; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCAnita extends AbstractGardenNpc { + private static final String TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTYwNDQxODU1MDQ5MCwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGQ0ZTlkMmE4Yzg4ZDBlZmQ3NTBlODUyNzMyYmEwNWZhM2YzMDJlMjA0ZTJiZGM4OGRhZDYwNWUyNTU3ZTJhNiIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"; + private static final String SIGNATURE = "kgTWKoHcjvKRS1D7bWAlG6yIYm+5y6xLnq4j1q8Q1UyeNyQkjaDBis+AiHGyQQQ+iyKC2iu3XTv1nXkxV1oYRwWFqg/aabA3S/FIEwppEZBrkDl+2zWNqFmwfsppPZ5zbsJHBvDm/c9RSlaKLEzeSO66KEyJDllRfcJWIGTg1xk5FxCf3hsgZ0QPynp3v/m0Pv9bmUtd88iuHus7kC76G41DDIQYm4xUOXY6E3i7AyqNX+fhl0EaLGg8DGm4mpfdFx0HVvOf5njXauhkTKCKMg7+WLQcLEHtPnnL8wSHOiNuzk8+tYbah2KzKJHjXSulWE4o5BGLgbbowPnLB3Nknzi2fwNnjqKNaoU1EZj3YpgPgpL4W6+fx7rScrt3gsGEso/7bHJwBBJLoYNdUL3XzJwI/z7sbFFukB28tL4kJ4Bc9eOduVopuaueioNcAHhPfxVp5wSrvNPq6r/c+yDBNHgOgcd3vn5iwWRh7Ls6tzY3bwUDqM7RUjIEhGb4shqDdMUSaS90eLlieZG9jpBVstMwHh5K2LXjIDeGH9sD9hFQaZ7G0OPvtlErRyoEXxnS1DxLs6Zcn/A4sxjFJbs4aoXweM7xpO2DmhdxCGYvMlAcj9KcCPkcYkwN5EM9Ws3EQWURIV37QNOWcd51vDmdH7f3GI6PVjbalS3esM9vgX8="; + + public NPCAnita() { + super("anita", "Anita", TEXTURE, SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + player.openView(new GUIAnita()); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCGardenVisitorSlot.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCGardenVisitorSlot.java new file mode 100644 index 0000000000..3bc8adc196 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCGardenVisitorSlot.java @@ -0,0 +1,104 @@ +package net.swofty.type.garden.npc; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.instance.Instance; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.garden.gui.GardenVisitorOfferView; +import net.swofty.type.generic.entity.npc.HypixelNPC; +import net.swofty.type.generic.entity.npc.configuration.HumanConfiguration; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; +import java.util.Map; + +public class NPCGardenVisitorSlot extends HypixelNPC { + private static final String TEXTURE = "eyJ0aW1lc3RhbXAiOjE1NTA2Nzg1OTkwMDQsInByb2ZpbGVJZCI6ImEyZjgzNDU5NWM4OTRhMjdhZGQzMDQ5NzE2Y2E5MTBjIiwicHJvZmlsZU5hbWUiOiJiUHVuY2giLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2M4Y2NkNGZkZjU4YjMwYWE4MzAxN2NmYTVmZWQ5NzcxOTZjMDI0YzhkMWEyNzYwMDRlOTA2OGU4ZWNiYjBiNzkifX19"; + private static final String SIGNATURE = "EzyAHb6TAVKVuO3R6cTt6eNJYXdU6C1fpPByuOEL/FUIIHqW5QpUnQLP7s3EjLhhzRagDi/eU/xGe09Ucsb7s6tSavn1jzfqwnmVG7C2FJ30ELl35y3pYbNKwmBl8I2fDY9pQrmfJbWRVhv9Gw8W4h8YRZARnW5PfVdsL1ddbTTsssaxapU8YTfUc88h2egnTD/bEHaqYEgfLBzjyMAyK9pDUIqe0NDmBJLbjPZXIVImRbMKanwgLRxmUkjGLONerb0HE8Kx6QoJEumoLOBrOLA5BJF7Jwghrv2d1W9S6hr89Ul6R8CnxQwHFfBMejccm0hLZein4DrKbiFHC8c/hs4jCoC4JT4rvOd/Yp8zNr3Y/dtUk5uTOguk/gYExI+p+1xc8HwTK3sK75LiFl+Ryu4LlKv5GBEznsnRHv1Ufeia3NeuVXDLi/W3zR8VG95Hf0lmKHdwJ/R9E56TxNYRh7wpma37ZTfEpUpKE1o7Z2m5c3jmDxLRdQg8dK1ZYMjlul36Qa8SXYTM4T+bdB1577M/44Vyde1NFVepYK0vRXDDNRal2LoDRM9buoTuN2taeP3pmt5C+pL554r7tWgOdCHUz51E9hwsOA9VCVxIA5eS+bgzSBLkWbXZYNo+zi/0bVr9OGdP2hCTJDsd0x7YEL2P7qribidVjnRLWWP8a2k="; + + private final int slot; + + public NPCGardenVisitorSlot(int slot) { + super(new HumanConfiguration() { + @Override + public String[] holograms(HypixelPlayer player) { + GardenData.GardenVisitorState visitor = currentVisitor(player, slot); + if (visitor == null) { + return new String[]{" ", "§e§lCLICK"}; + } + String displayName = displayName(player, visitor); + return new String[]{ + GardenGuiSupport.colorForRarity(visitor.getRarity()) + displayName, + "§e§lCLICK" + }; + } + + @Override + public Pos position(HypixelPlayer player) { + if (!(player instanceof SkyBlockPlayer skyBlockPlayer)) { + return GardenNpcSupport.hiddenPosition(); + } + return GardenNpcAnchorRegistry.getVisitorSlotAnchor(skyBlockPlayer, slot) + .map(GardenNpcAnchorRegistry.NpcAnchor::position) + .orElse(GardenNpcSupport.hiddenPosition()); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public boolean visible(HypixelPlayer player) { + return player instanceof SkyBlockPlayer skyBlockPlayer + && GardenNpcSupport.isVisibleOnGarden(skyBlockPlayer) + && currentVisitor(player, slot) != null + && GardenNpcAnchorRegistry.getVisitorSlotAnchor(skyBlockPlayer, slot).isPresent(); + } + + @Override + public String texture(HypixelPlayer player) { + return TEXTURE; + } + + @Override + public String signature(HypixelPlayer player) { + return SIGNATURE; + } + + @Override + public Instance instance(HypixelPlayer player) { + return GardenNpcSupport.instanceFor(player instanceof SkyBlockPlayer skyBlockPlayer ? skyBlockPlayer : null); + } + }); + this.slot = slot; + } + + @Override + public void onClick(NPCInteractEvent event) { + if (!(event.player() instanceof SkyBlockPlayer player)) { + return; + } + GardenData.GardenVisitorState visitor = currentVisitor(player, slot); + if (visitor != null) { + player.openView(new GardenVisitorOfferView(visitor.getVisitorId())); + } + } + + private static GardenData.GardenVisitorState currentVisitor(HypixelPlayer player, int slot) { + if (!(player instanceof SkyBlockPlayer skyBlockPlayer) || !skyBlockPlayer.isOnGarden()) { + return null; + } + List activeVisitors = GardenGuiSupport.visitors(skyBlockPlayer).getActiveVisitors(); + return slot <= 0 || slot > activeVisitors.size() ? null : activeVisitors.get(slot - 1); + } + + private static String displayName(HypixelPlayer player, GardenData.GardenVisitorState visitor) { + Map definition = net.swofty.type.garden.GardenServices.visitors().getVisitor(visitor.getVisitorId()); + return GardenConfigRegistry.getString(definition, "display_name", StringUtility.toNormalCase(visitor.getVisitorId())); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJacob.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJacob.java new file mode 100644 index 0000000000..5e0c912f8f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJacob.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.garden.gui.GUIJacobSFarmingContests; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCJacob extends AbstractGardenNpc { + private static final String TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTYwNDM3MDQyNDk4NiwKICAicHJvZmlsZUlkIiA6ICI0MWQzYWJjMmQ3NDk0MDBjOTA5MGQ1NDM0ZDAzODMxYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZWdha2xvb24iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2I4YmIxYjQ4ZjRiYWJjNjdjZTM5NTQ3MjA4ZmRiZWQ3MjJjYTU5OGNkZjMwNjgxZTM2N2M2MjQ3Y2FiMTkxOCIKICAgIH0KICB9Cn0="; + private static final String SIGNATURE = "pEkQ4zTK7/clvbGWw7JthccvquN+7wwY1rAIqUULhF6PLc8FWRwcVnwtd4K/j8xaTbe3ol8wayk8hraHtPPx6gxtLUbT/r/th9gsfJIBlbvcYJmrLnOI5yUHHYtxPjC2O9OTcxspyv2Ha0kF5Ua/RzOEjU4JN9HM5ni1g6c/U2mFgUnnj3SrNjr5pkIO0wZ27INHcswdVJuD8pC+yaflaSgKPV4CnoPFPp9uBvnVG9Knsz6oVNLno3ZUMojMYU5O3Nnkg8naNb/G2H1FBbwtgVxsjaprKbsqhrQHvFF9oia/Fz9I8IqGukLM0J9Dc3UANjORxeTZcqKkTnDM1wic7s52B5cuZZCQ2by5Di+km/hL2m8dJzVe7TbfS+FrK0galtQHD7jb7yXapQ7LqbutHTuElVCijrp0FlUE0rC+dUeBTbVFcXqo+V/cy2OVuv6DBJM87/6yogqlxyD3ZIetmJx9go42rIXwK97IgTtHt4nO/l14/kc46FL2ESe+P7Nqe/bJRmVDqEy5kuTSB7P4po1O2UHYv9Fydt0daxDrnm/CekKAg5EWh/m30RSKcXu84E85Y2FPW0U4jnmd2fcHaR3MCIY9os44GUEMm56xDKDNtqpRohTEhfo9ncLHTnC3+urN4QudtTbwjha31fvewzIDZKFsHAKakhyEG+OHehk="; + + public NPCJacob() { + super("jacob", "Jacob", TEXTURE, SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + player.openView(new GUIJacobSFarmingContests()); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJeff.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJeff.java new file mode 100644 index 0000000000..ca48462b8a --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCJeff.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.garden.gui.GUIManageChips; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCJeff extends AbstractGardenNpc { + private static final String TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTc1NjgyNTA3NDg4OSwKICAicHJvZmlsZUlkIiA6ICI0OWIzODUyNDdhMWY0NTM3YjBmN2MwZTFmMTVjMTc2NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJiY2QyMDMzYzYzZWM0YmY4IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzJhNGE3Nzg0MDQ0OTQzN2QyMWY5ZDVmMDQ3ZjUxODQxM2NiMmY2OWUzZWNiZmI5OTM4NjY0OWM5OTdjYTFlOTEiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ=="; + private static final String SIGNATURE = "Iw9yC7X3a2dPeEYsgmxARXA7iNQI6txFpVmQx5GBRF/SfVcFWDUbNLw3X8GzZp1hneHucwrTcgz1CLpdoJlawt0ZveveEMxJqrYPhvdGfJp1Pp1ap/Q70M19Ht24wxfG6eED8+SYsghk3mBrLs6pEQ63sg0SbZN6BRW50G6L2PGrgujBibdlj4m49CWE0gv3tqofWUH4yT/KXHN11bJ2LVURYMzSxl0EG6jrFKNkuufP3JJrK9oDaSt1msgxjqhK1O/cxv73nW2+na4ZKe9FDpGM7KF9fC4HirgeWu7UxOobNstHXcpfNZBPOLIry42nM2apEuH6jPm04tajLZJvYZqYyCS2LWc7uaIjhOrMm0nqZ7OKWj6E6m9szPPEeVq1MWT0nsp+pRqxBfqQld9EoN1sLKgzRGQ++qW+4bP7ceH6A1MoisJQ7ch8qF95F+FWDjjVcvV42iFB5ftXtMKTai8Vjw5ZcxfANk0Od+Nmxic4eC4QKm/tupvjnKS8rSPOZishmYh3B+aA/JOuzhqp844Sc4apPFBcLXCEK0+9oKHkVDkE3owHXHh3ktTs7naxgq92t3IVlxUtKGR6aVOSSEfp1FINXfdRBYuECpM4X/3H7zyE58UF0YmIKfYYh8jq/r2GTufXELY6kcc4Xy5XT8ZM8GQC1pJ/ih69hOOJKAQ="; + + public NPCJeff() { + super("jeff", "§bJeff", TEXTURE, SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + player.openView(new GUIManageChips()); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPamela.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPamela.java new file mode 100644 index 0000000000..47816db954 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPamela.java @@ -0,0 +1,31 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCPamela extends AbstractGardenNpc { + private static final String TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTY5NzU1NzEwODk1NywKICAicHJvZmlsZUlkIiA6ICJhNWZlYWViNDdhYjA0ZDZiYTk2ZjMyOGJjMDQ3MDZjMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJYeW5kcmEyIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzJhNjA0NmNmODQ2MjgxMzU1NjNlMmM0OWVlOWUxZmJiZDExNWY5MTZjOTZhN2E4NWVhOGU5NzM4NzY1ZGExNWIiCiAgICB9CiAgfQp9"; + private static final String SIGNATURE = "BRvZNsc8BauvahIbhZB48WNhH9is7pqpfQOib87m2uBk6UylprDpOd9Dt/1jNweJS4Fn/34EVNqQZIpn5cmbV5viHf+/VO+z66Dy5Jb1cYi2721q+sDXCKvdzuIQDrDtD7frrNTD9n9goJSk396rL8vjIkwt7D6rLLfdo0rkzhrTOm4OEVWsvPqdQNvwaeJ1RJFxiOj4Fs6CjwlB3ndZlI0h05cgC54EOrmvIJ5sMIAiPE+rXtvCI5WviTcZRpOGfhuP7OF9gUxns05hbT3jHXTiX6CmvXUL8lW7mo9MJlJ7qRwwvCC4ybGWTyde2d5RRXINKR/IWul4oWv8zsmmB482L368fsdI3A7vXIuxxijv1A9zNj8eydeAP/V/VOx/tQl9XD+Sk1FOwkjNsB0wSLjNoRH7kswejxh4Gjh5ZjIDsF8xdIDb438PQcJ6eK2WQd48dnNJ7yNGCLyAQ6YrkHvn6l+Q9nok4CDJeLSidIO+6aFpVCLeG0dGO3wSDm633wxo6a6rtawgpubKOR8gEXWXbmSENk6I5SWXAuGtk6FcLBXwhZRyN3jbbASyBBtJMzlLml+sOeLKfu7cDz1fSYVJBgamVwUAGgwDquoaHuwX2wEeCiI2cRoe/n8jMzuCvUCRlvb2AZmjSpaKFG6YNWbVxxlbZKvwmDHrgqsj3BI="; + + public NPCPamela() { + super("pamela", "§6Pamela", TEXTURE, SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + sendNPCMessage(player, "Anita handles medals. I keep an eye on the contests."); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPesthunterPhillip.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPesthunterPhillip.java new file mode 100644 index 0000000000..3b48d6c421 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCPesthunterPhillip.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.garden.gui.GUIPesthunter; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCPesthunterPhillip extends AbstractGardenNpc { + private static final String TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTY5NzU1NzA5MzQ5NSwKICAicHJvZmlsZUlkIiA6ICI4NmRlYmE5ZjBjNTI0MTA0YWFkMjUyOTdhMTAzNjFmNCIsCiAgInByb2ZpbGVOYW1lIiA6ICJFc2hlSG9yY2hhdGEiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQwZjY4OWZjNDdkM2Y3NzczZGJmYzM2YWY0NmE0MWUxNGU0M2I0ODBkODkxYmY0YjliZjFlYjgwNDM4MTc0MiIKICAgIH0KICB9Cn0="; + private static final String SIGNATURE = "BRvZNsc8BauvahIbhZB48WNhH9is7pqpfQOib87m2uBk6UylprDpOd9Dt/1jNweJS4Fn/34EVNqQZIpn5cmbV5viHf+/VO+z66Dy5Jb1cYi2721q+sDXCKvdzuIQDrDtD7frrNTD9n9goJSk396rL8vjIkwt7D6rLLfdo0rkzhrTOm4OEVWsvPqdQNvwaeJ1RJFxiOj4Fs6CjwlB3ndZlI0h05cgC54EOrmvIJ5sMIAiPE+rXtvCI5WviTcZRpOGfhuP7OF9gUxns05hbT3jHXTiX6CmvXUL8lW7mo9MJlJ7qRwwvCC4ybGWTyde2d5RRXINKR/IWul4oWv8zsmmB482L368fsdI3A7vXIuxxijv1A9zNj8eydeAP/V/VOx/tQl9XD+Sk1FOwkjNsB0wSLjNoRH7kswejxh4Gjh5ZjIDsF8xdIDb438PQcJ6eK2WQd48dnNJ7yNGCLyAQ6YrkHvn6l+Q9nok4CDJeLSidIO+6aFpVCLeG0dGO3wSDm633wxo6a6rtawgpubKOR8gEXWXbmSENk6I5SWXAuGtk6FcLBXwhZRyN3jbbASyBBtJMzlLml+sOeLKfu7cDz1fSYVJBgamVwUAGgwDquoaHuwX2wEeCiI2cRoe/n8jMzuCvUCRlvb2AZmjSpaKFG6YNWbVxxlbZKvwmDHrgqsj3BI="; + + public NPCPesthunterPhillip() { + super("phillip", "§6Phillip", TEXTURE, SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + player.openView(new GUIPesthunter()); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/npc/NPCSam.java b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCSam.java new file mode 100644 index 0000000000..6771f1f53f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/NPCSam.java @@ -0,0 +1,32 @@ +package net.swofty.type.garden.npc; + +import net.swofty.type.garden.gui.GUIDesk; +import net.swofty.type.generic.event.custom.NPCInteractEvent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class NPCSam extends AbstractGardenNpc { + private static final String BUILDER_TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTY2MTI0NzcyMjk1MiwKICAicHJvZmlsZUlkIiA6ICJjYmFkZmRmNTRkZTM0N2UwODQ3MjUyMDIyYTFkNGRkZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJvRml3aSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS85NWRjZmM4YjMyMDAzMjZiOGY2MWQ4MDYxNWM1YjUzNjk5N2ExMTg1NzBhNDdmMWQzNTMwNjMyOTY5MjFmZTEzIgogICAgfQogIH0KfQ=="; + private static final String BUILDER_SIGNATURE = "BgdB1A6pFJnqpQ5hCaEhFcEt4kZgvELFPg5Sx1bCXsUM956OmkAOJ3vqsyTAVcfhV/US7sKxDqwSh8JIV0tTV8fsC28cjur2f83cTw6ehlBKoERMRzwMV+ppLs6YjWIncTbst0D5CZqLpDs4ZhuR6u5uaDtbC716FJWFng+4YYn4XAapwiOqufUtmTdNjBuN1gmAJXtMhdcG5pNrm3SYO6KqVzo1a5eHcdXIkGp0mHFulAYZNy9S9RHuaEX1YNWWjyCTI+KQyv/mNfV/OfJtLH5e/BwPvL+Dcp7f+4wPeu3U2exiGpXPe2VOFRqFnaGP2URagVMj5BjVeuuiaY519YEGbYbR0SyG6HPSXVOM8A5NeVqGJ6FCcCg1ejfWNpIDS9mchIxgbufIzFOK1rD1E/JX051/IRAUrxW9hx8hne8X3/gOy84D6wS0j/KDjR7X7U1gZyAsTb1HqYdcNard1PNSLsdathh71APASTH0EZVm2JCh8exx7tNCKSW4jB9uvw0FugAdDUBKjWs1qO2Zrd7/IBePfW1uvoEU7TUnXRMKu0OB8yug7Y7CCE/jM1zMDnWRhn1QuF94+se6R2swmDJM8NA/exsDmzFMXi+ndw+bOfJYdyzM+s3H6AgvOnmOtgrZBIkLYSMlvSMigxl2Vd/9rRdVn4wFyv4/Dd1aPho="; + + public NPCSam() { + super("sam", "§bSam", BUILDER_TEXTURE, BUILDER_SIGNATURE); + } + + @Override + public void onClick(NPCInteractEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + if (isInDialogue(player)) { + return; + } + if (!hasSpoken(player)) { + setDialogue(player, "hello").thenRun(() -> markSpoken(player)); + return; + } + player.openView(new GUIDesk()); + } + + @Override + protected DialogueSet[] dialogues(net.swofty.type.generic.user.HypixelPlayer player) { + return configuredDialogues(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestRuntime.java b/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestRuntime.java new file mode 100644 index 0000000000..e44d8c46ec --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestRuntime.java @@ -0,0 +1,106 @@ +package net.swofty.type.garden.pest; + +import net.minestom.server.timer.ExecutionType; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +public final class GardenPestRuntime { + private static final long OFFLINE_SPAWN_INTERVAL_MS = 3_600_000L; + + private GardenPestRuntime() { + } + + public static void start(Scheduler scheduler) { + scheduler.submitTask(() -> { + tickAllGardens(); + return TaskSchedule.seconds(1); + }, ExecutionType.TICK_END); + } + + private static void tickAllGardens() { + Map> playersByProfile = SkyBlockGenericLoader.getLoadedPlayers().stream() + .filter(SkyBlockPlayer::isOnGarden) + .filter(player -> player.getSkyblockDataHandler() != null) + .collect(Collectors.groupingBy(player -> player.getSkyblockDataHandler().getCurrentProfileId())); + + playersByProfile.values().forEach(GardenPestRuntime::tickProfile); + } + + private static void tickProfile(List viewers) { + if (viewers.isEmpty()) { + return; + } + + SkyBlockPlayer primary = viewers.getFirst(); + GardenData.GardenCoreData core = GardenGuiSupport.core(primary); + GardenData.GardenPestsData pests = GardenGuiSupport.pests(primary); + long now = System.currentTimeMillis(); + + if (pests.getLastSpawnCheckAt() <= 0L) { + pests.setLastSpawnCheckAt(now); + return; + } + + long offlineReference = Math.max(pests.getLastSpawnCheckAt(), core.getLastActiveAt()); + long offlineElapsed = Math.max(0L, now - offlineReference); + if (offlineElapsed > 0L) { + pests.setOfflineAccumulatorMs(pests.getOfflineAccumulatorMs() + offlineElapsed); + } + + int spawned = 0; + while (pests.getOfflineAccumulatorMs() >= OFFLINE_SPAWN_INTERVAL_MS && core.getLevel() >= GardenConfigRegistry.getInt( + GardenConfigRegistry.getConfig("pests.yml"), + "start_garden_level", + 5 + )) { + if (!spawnOfflinePest(primary, pests, core, now)) { + break; + } + pests.setOfflineAccumulatorMs(pests.getOfflineAccumulatorMs() - OFFLINE_SPAWN_INTERVAL_MS); + spawned++; + } + + if (spawned > 0) { + int totalSpawned = spawned; + viewers.forEach(player -> player.sendMessage("§cWhile you were away, §6" + totalSpawned + " §cpest" + (totalSpawned == 1 ? "" : "s") + " infested your Garden.")); + } + pests.setLastSpawnCheckAt(now); + } + + private static boolean spawnOfflinePest( + SkyBlockPlayer primary, + GardenData.GardenPestsData pests, + GardenData.GardenCoreData core, + long now + ) { + List> eligible = GardenServices.pests().getPests().stream() + .filter(entry -> !GardenConfigRegistry.getBoolean(entry, "trap_only", false)) + .filter(entry -> core.getLevel() >= GardenConfigRegistry.getInt(entry, "garden_level", 5)) + .toList(); + if (eligible.isEmpty()) { + return false; + } + + Map chosen = eligible.get(ThreadLocalRandom.current().nextInt(eligible.size())); + GardenData.GardenPestState pest = new GardenData.GardenPestState(); + pest.setPestId(GardenConfigRegistry.getString(chosen, "id", "")); + pest.setPlotId("central"); + pest.setSpawnedAt(now); + pest.setOfflineSpawned(true); + pests.getActivePests().add(pest); + primary.sendMessage("§cOffline pest spawned: §6" + GardenConfigRegistry.getString(chosen, "display_name", "Pest")); + return true; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestService.java b/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestService.java new file mode 100644 index 0000000000..9ace2449dd --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/pest/GardenPestService.java @@ -0,0 +1,76 @@ +package net.swofty.type.garden.pest; + +import net.swofty.type.garden.config.GardenConfigRegistry; + +import java.util.List; +import java.util.Map; + +public class GardenPestService { + private Map config = Map.of(); + private List> pests = List.of(); + private List> drops = List.of(); + + public void reload() { + config = GardenConfigRegistry.getConfig("pests.yml"); + pests = GardenConfigRegistry.getMapList(config, "registry"); + drops = GardenConfigRegistry.getMapList(GardenConfigRegistry.getConfig("pest_drops.yml"), "drops"); + } + + public double getBaseSpawnChance() { + return GardenConfigRegistry.getDouble(config, "base_spawn_chance", 0.002D); + } + + public long getBaseCooldownSeconds() { + return GardenConfigRegistry.getLong(config, "base_cooldown_seconds", 300); + } + + public long calculateSpawnCooldownSeconds(CooldownModifiers modifiers) { + double reduction = 0D; + reduction += modifiers.finnegan() ? 0.20D : 0D; + reduction += modifiers.pesthunterPieces() * 0.10D; + reduction += modifiers.squeakyPieces() * 0.025D; + reduction += modifiers.hasPestVest() ? 0.15D : 0D; + + double cooldown = getBaseCooldownSeconds() * Math.max(0D, 1D - reduction); + if (modifiers.repellentMax()) { + cooldown *= 4D; + } else if (modifiers.repellent()) { + cooldown *= 2D; + } + cooldown = Math.max(75D, Math.min(1200D, cooldown)); + return Math.round(cooldown); + } + + public int getFortunePenaltyPercent(int pestCount, int bonusPestChance) { + int thresholdShift = Math.max(0, bonusPestChance / 100); + int effectivePests = pestCount - thresholdShift; + if (effectivePests <= 3) { + return 0; + } + return switch (effectivePests) { + case 4 -> 5; + case 5 -> 15; + case 6 -> 30; + case 7 -> 50; + default -> 75; + }; + } + + public List> getPests() { + return pests; + } + + public List> getDrops() { + return drops; + } + + public record CooldownModifiers( + boolean finnegan, + int pesthunterPieces, + boolean hasPestVest, + int squeakyPieces, + boolean repellent, + boolean repellentMax + ) { + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/plot/GardenPlotService.java b/type.garden/src/main/java/net/swofty/type/garden/plot/GardenPlotService.java new file mode 100644 index 0000000000..887e6adc5e --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/plot/GardenPlotService.java @@ -0,0 +1,204 @@ +package net.swofty.type.garden.plot; + +import net.hollowcube.schem.Schematic; +import net.hollowcube.schem.util.Rotation; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.SharedInstance; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.type.garden.config.GardenBarnSkinDefinition; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.config.GardenPlotDefinition; +import net.swofty.type.garden.config.GardenRegion; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.garden.world.GardenAssetRegistry; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class GardenPlotService { + private final SkyBlockGarden garden; + private final Map plots = new LinkedHashMap<>(); + private GardenRegion barnSwapRegion = new GardenRegion(-33, 35, -47, -5); + private final int BASE_Y = 67; + + public GardenPlotService(SkyBlockGarden garden) { + this.garden = garden; + reload(); + } + + public void reload() { + plots.clear(); + Map config = GardenConfigRegistry.getConfig("plots.yml"); + Map world = GardenConfigRegistry.getSection(config, "world"); + Map barnSwap = GardenConfigRegistry.getSection(world, "barn_swap_region"); + barnSwapRegion = new GardenRegion( + GardenConfigRegistry.getInt(barnSwap, "min_x", -33), + GardenConfigRegistry.getInt(barnSwap, "max_x", 35), + GardenConfigRegistry.getInt(barnSwap, "min_z", -47), + GardenConfigRegistry.getInt(barnSwap, "max_z", -5) + ); + + for (Map plot : GardenConfigRegistry.getMapList(config, "plots")) { + Map bounds = GardenConfigRegistry.getSection(plot, "bounds"); + GardenPlotDefinition definition = new GardenPlotDefinition( + GardenConfigRegistry.getString(plot, "id", "unknown"), + GardenConfigRegistry.getString(plot, "display_name", GardenConfigRegistry.getString(plot, "id", "Unknown")), + GardenConfigRegistry.getString(plot, "group", "A"), + GardenConfigRegistry.getInt(plot, "garden_level", 0), + GardenConfigRegistry.getBoolean(plot, "default_unlocked", false), + new GardenRegion( + GardenConfigRegistry.getInt(bounds, "min_x", 0), + GardenConfigRegistry.getInt(bounds, "max_x", 0), + GardenConfigRegistry.getInt(bounds, "min_z", 0), + GardenConfigRegistry.getInt(bounds, "max_z", 0) + ) + ); + plots.put(definition.id(), definition); + } + } + + public boolean canEdit(Point point, GardenData.GardenCoreData coreData, boolean barnSwapInProgress) { + if (!garden.isWithinBounds(point)) { + return false; + } + if (isInBarnSwapRegion(point)) { + return false; + } + GardenPlotDefinition plot = getPlotAt(point); + if (plot == null) { + return false; + } + return plot.defaultUnlocked() || coreData.getUnlockedPlots().contains(plot.id()); + } + + public boolean isUnlocked(Point point, GardenData.GardenCoreData coreData) { + GardenPlotDefinition plot = getPlotAt(point); + if (plot == null) { + return false; + } + return plot.defaultUnlocked() || coreData.getUnlockedPlots().contains(plot.id()); + } + + public GardenPlotDefinition getPlotAt(Point point) { + for (GardenPlotDefinition definition : plots.values()) { + if (definition.region().contains(point)) { + return definition; + } + } + return null; + } + + public boolean isInBarnSwapRegion(Point point) { + return barnSwapRegion.contains(point); + } + + public CompletableFuture applyBarnSkin(String skinId) { + CompletableFuture future = new CompletableFuture<>(); + GardenBarnSkinDefinition definition = getBarnSkinDefinition(skinId); + if (definition == null) { + future.completeExceptionally(new IllegalArgumentException("Unknown barn skin: " + skinId)); + return future; + } + if (garden.isBarnSwapInProgress()) { + future.completeExceptionally(new IllegalStateException("A barn skin swap is already running")); + return future; + } + + SharedInstance instance = garden.getGardenInstance(); + if (instance == null) { + future.completeExceptionally(new IllegalStateException("Garden instance is not loaded")); + return future; + } + + Schematic schematic = GardenAssetRegistry.getBarnSkinSchematic(definition); + int delayTicks = Math.max(1, Math.round((30f * 20f) / barnSwapRegion.width())); + final int[] currentX = {barnSwapRegion.minX()}; + + garden.setBarnSwapInProgress(true); + MinecraftServer.getSchedulerManager().submitTask(() -> { + if (currentX[0] > barnSwapRegion.maxX()) { + garden.setBarnSwapInProgress(false); + future.complete(null); + return TaskSchedule.stop(); + } + + pasteSlice(instance, schematic, definition, currentX[0]); + currentX[0]++; + return TaskSchedule.tick(delayTicks); + }); + + return future; + } + + private void pasteSlice(SharedInstance instance, Schematic schematic, GardenBarnSkinDefinition definition, int targetX) { + schematic.forEachBlock(Rotation.NONE, (point, block) -> { + int worldX = barnSwapRegion.minX() + point.blockX() + definition.offsetX(); + int worldY = BASE_Y + point.blockY() + definition.offsetY(); + int worldZ = barnSwapRegion.minZ() + point.blockZ() + definition.offsetZ(); + + if (worldX != targetX) { + return; + } + if (worldZ < barnSwapRegion.minZ() || worldZ > barnSwapRegion.maxZ()) { + return; + } + instance.setBlock(worldX, worldY, worldZ, block); + }); + } + + private int detectBarnBaseY(SharedInstance instance) { + int detected = Integer.MAX_VALUE; + for (int x = barnSwapRegion.minX(); x <= barnSwapRegion.maxX(); x++) { + for (int z = barnSwapRegion.minZ(); z <= barnSwapRegion.maxZ(); z++) { + for (int y = -64; y <= 255; y++) { + if (!instance.getBlock(x, y, z).isAir()) { + detected = Math.min(detected, y); + break; + } + } + } + } + return detected == Integer.MAX_VALUE ? 0 : detected; + } + + private GardenBarnSkinDefinition getBarnSkinDefinition(String skinId) { + return getBarnSkins().stream() + .filter(definition -> definition.id().equalsIgnoreCase(skinId)) + .findFirst() + .orElse(null); + } + + public List getBarnSkins() { + Map config = GardenConfigRegistry.getConfig("barn_skins.yml"); + List skins = new ArrayList<>(); + for (Map entry : GardenConfigRegistry.getMapList(config, "skins")) { + Map offsets = GardenConfigRegistry.getSection(entry, "offsets"); + skins.add(new GardenBarnSkinDefinition( + GardenConfigRegistry.getString(entry, "id", "default"), + GardenConfigRegistry.getString(entry, "display_name", "Default"), + GardenConfigRegistry.getString(entry, "rarity", "COMMON"), + GardenConfigRegistry.getString(entry, "schematic", "default.litematic"), + GardenConfigRegistry.getString(entry, "unlock_source", ""), + GardenConfigRegistry.getInt(offsets, "x", 0), + GardenConfigRegistry.getInt(offsets, "y", 0), + GardenConfigRegistry.getInt(offsets, "z", 0) + )); + } + skins.sort(Comparator.comparing(GardenBarnSkinDefinition::id)); + return skins; + } + + public List getPlots() { + return new ArrayList<>(plots.values()); + } + + public GardenRegion getBarnSwapRegion() { + return barnSwapRegion; + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/shop/GardenDeskService.java b/type.garden/src/main/java/net/swofty/type/garden/shop/GardenDeskService.java new file mode 100644 index 0000000000..045501dfd6 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/shop/GardenDeskService.java @@ -0,0 +1,57 @@ +package net.swofty.type.garden.shop; + +import net.swofty.type.garden.config.GardenConfigRegistry; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GardenDeskService { + private List> skyMartEntries = List.of(); + private List> cropUpgradeTiers = List.of(); + private final Map> cropGroups = new HashMap<>(); + + public void reload() { + Map copperShop = GardenConfigRegistry.getConfig("copper_shop.yml"); + skyMartEntries = GardenConfigRegistry.getMapList(copperShop, "skymart"); + + cropGroups.clear(); + GardenConfigRegistry.getSection(copperShop, "categories").forEach((key, value) -> { + if (value instanceof List list) { + cropGroups.put(key, list.stream().map(String::valueOf).toList()); + } + }); + + Map cropUpgrades = GardenConfigRegistry.getConfig("crop_upgrades.yml"); + cropUpgradeTiers = GardenConfigRegistry.getMapList(cropUpgrades, "tiers"); + } + + public List> getSkyMartEntries() { + return skyMartEntries; + } + + public List> getSkyMartEntries(String category) { + return skyMartEntries.stream() + .filter(entry -> GardenConfigRegistry.getString(entry, "category", "").equalsIgnoreCase(category)) + .toList(); + } + + public Map getSkyMartEntry(String id) { + return skyMartEntries.stream() + .filter(entry -> GardenConfigRegistry.getString(entry, "id", "").equalsIgnoreCase(id)) + .findFirst() + .orElse(Map.of()); + } + + public List> getCropUpgradeTiers() { + return cropUpgradeTiers; + } + + public int getCropUpgradeFortunePerTier() { + return 5; + } + + public List getCategoryEntries(String category) { + return cropGroups.getOrDefault(category, List.of()); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/user/SkyBlockGarden.java b/type.garden/src/main/java/net/swofty/type/garden/user/SkyBlockGarden.java new file mode 100644 index 0000000000..4dfac09dd3 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/user/SkyBlockGarden.java @@ -0,0 +1,218 @@ +package net.swofty.type.garden.user; + +import lombok.Getter; +import lombok.Setter; +import net.hollowcube.polar.PolarLoader; +import net.hollowcube.polar.PolarReader; +import net.hollowcube.polar.PolarWorld; +import net.hollowcube.polar.PolarWriter; +import net.kyori.adventure.key.Key; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.instance.InstanceManager; +import net.minestom.server.instance.SharedInstance; +import net.minestom.server.registry.RegistryKey; +import net.minestom.server.timer.ExecutionType; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; +import net.minestom.server.world.DimensionType; +import net.swofty.type.garden.plot.GardenPlotService; +import net.swofty.type.garden.world.GardenAssetRegistry; +import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; +import net.swofty.type.skyblockgeneric.data.SkyBlockDataHandler; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenCore; +import net.swofty.type.skyblockgeneric.data.monogdb.CoopDatabase; +import net.swofty.type.skyblockgeneric.data.monogdb.GardenDatabase; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.garden.SkyBlockGardenHandle; +import net.swofty.type.skyblockgeneric.garden.WorldBuildLimits; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; +import org.bson.types.Binary; +import org.jetbrains.annotations.Nullable; +import org.tinylog.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@Getter +public class SkyBlockGarden implements SkyBlockGardenHandle { + public static final int CURRENT_GARDEN_VERSION = 1; + private static final WorldBuildLimits BUILD_LIMITS = new WorldBuildLimits(-240, 239, -240, 239); + private static final Map LOADED_GARDENS = new HashMap<>(); + + private final GardenDatabase database; + private final CoopDatabase.Coop coop; + private final UUID profileId; + private final GardenPlotService plotService; + + private boolean created = false; + private SharedInstance gardenInstance; + private PolarWorld world; + @Setter + private boolean barnSwapInProgress = false; + @Setter + private long lastSaved = 0; + @Setter + private int gardenVersion = CURRENT_GARDEN_VERSION; + + public SkyBlockGarden(UUID profileId) { + this.profileId = profileId; + this.database = new GardenDatabase(profileId.toString()); + this.coop = CoopDatabase.getFromMemberProfile(profileId); + this.plotService = new GardenPlotService(this); + LOADED_GARDENS.put(profileId, this); + } + + public CompletableFuture getSharedInstance() { + InstanceManager manager = MinecraftServer.getInstanceManager(); + CompletableFuture future = new CompletableFuture<>(); + + new Thread(() -> { + if (created) { + future.complete(gardenInstance); + return; + } + + RegistryKey dimensionTypeKey = MinecraftServer.getDimensionTypeRegistry().getKey( + Key.key("skyblock:island") + ); + InstanceContainer temporaryInstance = manager.createInstanceContainer(dimensionTypeKey); + gardenInstance = manager.createSharedInstance(temporaryInstance); + + List onlinePlayers; + if (coop != null) { + onlinePlayers = coop.getOnlineMembers().stream() + .filter(player -> player.getSkyblockDataHandler() != null) + .filter(player -> profileId.equals(player.getSkyblockDataHandler().getCurrentProfileId())) + .toList(); + } else { + onlinePlayers = SkyBlockGenericLoader.getLoadedPlayers().stream() + .filter(player -> player.getSkyblockDataHandler() != null) + .filter(player -> profileId.equals(player.getSkyblockDataHandler().getCurrentProfileId())) + .toList(); + } + + if (!database.exists() || !database.has("polar_blob")) { + gardenVersion = CURRENT_GARDEN_VERSION; + try { + world = PolarReader.read(Files.readAllBytes(GardenAssetRegistry.getGardenSeed())); + } catch (IOException e) { + Logger.error(e, "Failed to create Garden world"); + throw new RuntimeException("Failed to create Garden world", e); + } + lastSaved = System.currentTimeMillis(); + } else { + if (database.has("version")) { + gardenVersion = (int) database.get("version", Integer.class); + } + world = PolarReader.read(((Binary) database.get("polar_blob", Binary.class)).getData()); + lastSaved = (long) database.get("last_saved", Long.class); + if (gardenVersion < CURRENT_GARDEN_VERSION) { + gardenVersion = CURRENT_GARDEN_VERSION; + } + } + + temporaryInstance.setChunkLoader(new PolarLoader(world)); + created = true; + + future.complete(gardenInstance); + onlinePlayers.forEach(SkyBlockPlayer::setReadyForEvents); + }).start(); + + return future; + } + + public void runVacantCheck() { + if (gardenInstance == null) { + return; + } + + if (gardenInstance.getPlayers().isEmpty()) { + save(); + created = false; + gardenInstance.getChunks().forEach(gardenInstance::unloadChunk); + gardenInstance = null; + world = null; + } + } + + public CompletableFuture changeBarnSkin(String skinId) { + return plotService.applyBarnSkin(skinId); + } + + private void save() { + if (world == null || gardenInstance == null) { + return; + } + new PolarLoader(world).saveInstance(gardenInstance); + database.insertOrUpdate("polar_blob", new Binary(PolarWriter.write(world))); + database.insertOrUpdate("last_saved", System.currentTimeMillis()); + database.insertOrUpdate("version", gardenVersion); + lastSaved = System.currentTimeMillis(); + } + + @Override + public WorldBuildLimits getBuildLimits() { + return BUILD_LIMITS; + } + + @Override + public boolean canEdit(net.minestom.server.coordinate.Point point) { + GardenData.GardenCoreData coreData = resolveCoreData(); + return plotService.canEdit(point, coreData, barnSwapInProgress); + } + + @Override + public Optional getDeniedBuildMessage(Point point) { + if (!isWithinBounds(point)) { + return SkyBlockGardenHandle.super.getDeniedBuildMessage(point); + } + if (plotService.isInBarnSwapRegion(point)) { + return Optional.empty(); + } + if (!plotService.isUnlocked(point, resolveCoreData())) { + return Optional.of("§cYou haven't unlocked this area yet!"); + } + return Optional.of("§cYou can't edit that part of your Garden yet!"); + } + + private GardenData.GardenCoreData resolveCoreData() { + SkyBlockPlayer viewer = getOnlineViewer(); + if (viewer == null) { + return new GardenData.GardenCoreData(); + } + return viewer.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_CORE, DatapointGardenCore.class) + .getValue(); + } + + private @Nullable SkyBlockPlayer getOnlineViewer() { + return SkyBlockGenericLoader.getLoadedPlayers().stream() + .filter(player -> player.getSkyblockDataHandler() != null) + .filter(player -> profileId.equals(player.getSkyblockDataHandler().getCurrentProfileId())) + .findFirst() + .orElse(null); + } + + public static @Nullable SkyBlockGarden getGarden(UUID profileId) { + return LOADED_GARDENS.get(profileId); + } + + public static void runVacantLoop(Scheduler scheduler) { + scheduler.submitTask(() -> { + SkyBlockGenericLoader.getLoadedPlayers().forEach(player -> { + if (player.isOnGarden() && player.getSkyBlockGarden() instanceof SkyBlockGarden garden) { + garden.runVacantCheck(); + } + }); + return TaskSchedule.tick(4); + }, ExecutionType.TICK_END); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenBarnRuntime.java b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenBarnRuntime.java new file mode 100644 index 0000000000..ed17ea90ca --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenBarnRuntime.java @@ -0,0 +1,158 @@ +package net.swofty.type.garden.visitor; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.instance.Instance; +import net.minestom.server.timer.ExecutionType; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.type.garden.gui.GUIComposter; +import net.swofty.type.garden.gui.GUIDesk; +import net.swofty.type.garden.gui.GUIVisitorLogbook; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.garden.npc.GardenNpcAnchorRegistry; +import net.swofty.type.garden.user.SkyBlockGarden; +import net.swofty.type.generic.entity.InteractionEntity; +import net.swofty.type.generic.entity.hologram.ServerHolograms; +import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; +import net.swofty.type.skyblockgeneric.furniture.Furniture; +import net.swofty.type.skyblockgeneric.furniture.FurnitureLoadException; +import net.swofty.type.skyblockgeneric.furniture.FurnitureNotPresent; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; +import org.tinylog.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public final class GardenBarnRuntime { + private static final Map STATES = new ConcurrentHashMap<>(); + private static final Set DIRTY_PROFILES = ConcurrentHashMap.newKeySet(); + + private GardenBarnRuntime() { + } + + public static void start(Scheduler scheduler) { + scheduler.submitTask(() -> { + tickAllGardens(); + return TaskSchedule.tick(2); + }, ExecutionType.TICK_END); + } + + public static void requestImmediateSync(SkyBlockPlayer player) { + if (player == null || player.getSkyblockDataHandler() == null) { + return; + } + DIRTY_PROFILES.add(player.getSkyblockDataHandler().getCurrentProfileId()); + } + + private static void tickAllGardens() { + Map> playersByProfile = SkyBlockGenericLoader.getLoadedPlayers().stream() + .filter(SkyBlockPlayer::isOnGarden) + .filter(player -> player.getSkyblockDataHandler() != null) + .collect(Collectors.groupingBy(player -> player.getSkyblockDataHandler().getCurrentProfileId())); + + playersByProfile.forEach(GardenBarnRuntime::tickProfile); + + STATES.entrySet().removeIf(entry -> { + if (playersByProfile.containsKey(entry.getKey())) { + return false; + } + entry.getValue().destroy(); + return true; + }); + } + + private static void tickProfile(UUID profileId, List viewers) { + if (viewers.isEmpty()) { + return; + } + + SkyBlockPlayer primary = viewers.getFirst(); + if (!(primary.getSkyBlockGarden() instanceof SkyBlockGarden garden) || garden.getGardenInstance() == null) { + return; + } + + BarnState state = STATES.computeIfAbsent(profileId, ignored -> new BarnState()); + Instance instance = garden.getGardenInstance(); + String skinId = GardenGuiSupport.core(primary).getSelectedBarnSkin(); + boolean dirty = DIRTY_PROFILES.remove(profileId); + + if (dirty || state.instance != instance || !Objects.equals(state.skinId, skinId)) { + state.rebuildStatic(primary, instance, skinId); + } + + } + + private static final class BarnState { + private final Map interactables = new HashMap<>(); + private Instance instance; + private String skinId = ""; + + private void rebuildStatic(SkyBlockPlayer player, Instance nextInstance, String nextSkinId) { + destroyStatic(); + instance = nextInstance; + skinId = nextSkinId; + + createInteractable(player, "desk", "§bDesk"); + createInteractable(player, "composter", "§aComposter"); + createInteractable(player, "visitor_logbook", "§aVisitor Logbook"); + } + + private void createInteractable(SkyBlockPlayer player, String anchorId, String label) { + GardenNpcAnchorRegistry.getInteractionAnchor(player, anchorId).ifPresent(anchor -> { + Pos position = anchor.position(); + ServerHolograms.ExternalHologram hologram = ServerHolograms.ExternalHologram.builder() + .instance(instance) + .pos(position.add(0, anchor.offsetY() != null ? anchor.offsetY() : 0.6, 0)) + .text(new String[]{label, "§e§lCLICK"}) + .build(); + ServerHolograms.addExternalHologram(hologram); + + InteractionEntity interaction = new InteractionEntity(1.4f, 2f, (viewer, _) -> { + if (!(viewer instanceof SkyBlockPlayer skyBlockPlayer)) { + return; + } + if ("desk".equals(anchorId)) { + skyBlockPlayer.openView(new GUIDesk()); + } else if ("composter".equals(anchorId)) { + skyBlockPlayer.openView(new GUIComposter()); + } else if ("visitor_logbook".equals(anchorId)) { + skyBlockPlayer.openView(new GUIVisitorLogbook()); + } + }); + interaction.setInstance(instance, position); + + try { + Furniture.load(player.getInstance(), anchorId, position); + } catch (FurnitureLoadException | FurnitureNotPresent _) { + Logger.warn("Failed to load Garden furniture"); + } + + interactables.put(anchorId, new GardenInteractable(hologram, interaction)); + }); + } + + private void destroyStatic() { + interactables.values().forEach(GardenInteractable::destroy); + interactables.clear(); + } + + private void destroy() { + destroyStatic(); + instance = null; + skinId = ""; + } + } + + private record GardenInteractable(ServerHolograms.ExternalHologram hologram, InteractionEntity interaction) { + private void destroy() { + ServerHolograms.removeExternalHologram(hologram); + interaction.remove(); + } + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorRuntime.java b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorRuntime.java new file mode 100644 index 0000000000..7115843cc6 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorRuntime.java @@ -0,0 +1,432 @@ +package net.swofty.type.garden.visitor; + +import net.minestom.server.timer.ExecutionType; +import net.minestom.server.timer.Scheduler; +import net.minestom.server.timer.TaskSchedule; +import net.swofty.commons.StringUtility; +import net.swofty.type.garden.GardenCropRegistry; +import net.swofty.type.garden.GardenServices; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.garden.gui.GardenGuiSupport; +import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.skill.SkillCategories; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public final class GardenVisitorRuntime { + private static final Pattern ANY_PATTERN = Pattern.compile("^ANY(?:\\s+(\\d+))?$", Pattern.CASE_INSENSITIVE); + private static final List RARITY_ORDER = List.of("UNCOMMON", "RARE", "LEGENDARY", "MYTHIC", "SPECIAL"); + private GardenVisitorRuntime() { + } + + public static void start(Scheduler scheduler) { + scheduler.submitTask(() -> { + tickAllGardens(); + return TaskSchedule.seconds(1); + }, ExecutionType.TICK_END); + } + + public static GardenData.GardenVisitorState createVisitorState(SkyBlockPlayer player, String visitorId) { + if (player == null) { + return null; + } + Map definition = GardenServices.visitors().getVisitor(visitorId); + if (definition.isEmpty()) { + return null; + } + return buildVisitorState( + player, + GardenGuiSupport.visitors(player), + GardenGuiSupport.core(player), + definition, + System.currentTimeMillis() + ); + } + + private static void tickAllGardens() { + Map> playersByProfile = SkyBlockGenericLoader.getLoadedPlayers().stream() + .filter(SkyBlockPlayer::isOnGarden) + .filter(player -> player.getSkyblockDataHandler() != null) + .collect(Collectors.groupingBy(player -> player.getSkyblockDataHandler().getCurrentProfileId())); + + playersByProfile.values().forEach(GardenVisitorRuntime::tickProfile); + } + + private static void tickProfile(List viewers) { + if (viewers.isEmpty()) { + return; + } + + SkyBlockPlayer primary = viewers.getFirst(); + GardenData.GardenVisitorsData visitors = GardenGuiSupport.visitors(primary); + GardenData.GardenCoreData core = GardenGuiSupport.core(primary); + long now = System.currentTimeMillis(); + + if (visitors.getLastProcessedAt() <= 0L) { + visitors.setLastProcessedAt(now); + } + if (visitors.getNextArrivalAt() <= 0L) { + visitors.setNextArrivalAt(now + arrivalMillis(core)); + } + + long realElapsed = Math.max(0L, now - visitors.getLastProcessedAt()); + if (realElapsed > 0L && isActivelyFarming(visitors, now)) { + int farmingSpeedup = Math.max(1, GardenServices.visitors().getFarmingSpeedupMultiplier()); + long bonusReduction = realElapsed * (farmingSpeedup - 1L); + visitors.setNextArrivalAt(visitors.getNextArrivalAt() - bonusReduction); + } + + int maxCapacity = GardenServices.visitors().getMaxVisibleVisitors() + GardenServices.visitors().getMaxQueuedVisitors(); + while (visitors.getActiveVisitors().size() + visitors.getQueuedVisitors().size() < maxCapacity + && now >= visitors.getNextArrivalAt()) { + if (!spawnNextVisitor(primary, viewers, visitors, core, now)) { + visitors.setNextArrivalAt(now + arrivalMillis(core)); + break; + } + visitors.setNextArrivalAt(visitors.getNextArrivalAt() + arrivalMillis(core)); + } + + visitors.setLastProcessedAt(now); + core.setLastActiveAt(now); + } + + private static boolean spawnNextVisitor( + SkyBlockPlayer primary, + List viewers, + GardenData.GardenVisitorsData visitors, + GardenData.GardenCoreData core, + long now + ) { + List> candidates = GardenServices.visitors().getVisitors().stream() + .filter(definition -> canVisit(primary, visitors, core, definition)) + .toList(); + if (candidates.isEmpty()) { + return false; + } + + Map chosen = chooseWeighted(candidates); + GardenData.GardenVisitorState state = buildVisitorState(primary, visitors, core, chosen, now); + + if (visitors.getActiveVisitors().size() < GardenServices.visitors().getMaxVisibleVisitors()) { + visitors.getActiveVisitors().add(state); + } else { + state.setQueued(true); + visitors.getQueuedVisitors().add(state); + } + + String visitorId = state.getVisitorId(); + visitors.getVisitCounts().merge(visitorId, 1, Integer::sum); + visitors.getLogbookEntries().add(visitorId); + + String displayName = GardenConfigRegistry.getString(chosen, "display_name", StringUtility.toNormalCase(visitorId)); + viewers.forEach(player -> player.sendMessage("§aVisitor §e" + displayName + " §ahas arrived at your Desk.")); + GardenBarnRuntime.requestImmediateSync(primary); + return true; + } + + private static GardenData.GardenVisitorState buildVisitorState( + SkyBlockPlayer player, + GardenData.GardenVisitorsData visitors, + GardenData.GardenCoreData core, + Map definition, + long now + ) { + String visitorId = GardenConfigRegistry.getString(definition, "id", ""); + String rarity = GardenConfigRegistry.getString(definition, "rarity", "UNCOMMON").toUpperCase(); + boolean firstVisit = !visitors.getVisitCounts().containsKey(visitorId) && !visitors.getServedCounts().containsKey(visitorId); + + int[] baseRange = GardenServices.visitors().getBaseItemsRange(core.getLevel()); + int baseItems = ThreadLocalRandom.current().nextInt(baseRange[0], baseRange[1] + 1); + + List wantedItems = resolveWantedItems(definition, firstVisit); + List requests = new ArrayList<>(); + double farmingXp = 0D; + int copper = 0; + + for (WantedRequest wantedItem : wantedItems) { + double quantityMultiplier = GardenServices.visitors().getQuantityMultiplier(wantedItem.itemId()); + int rawAmount = wantedItem.fixedAmount() > 0 + ? wantedItem.fixedAmount() + : Math.max(1, (int) Math.round( + baseItems * quantityMultiplier * GardenServices.visitors().getRequestMultiplier(rarity) + )); + GardenVisitorService.CompactedRequest compactedRequest = GardenServices.visitors().compactRequest(wantedItem.itemId(), rawAmount); + + GardenData.GardenRequest request = new GardenData.GardenRequest(); + request.setItemId(compactedRequest.itemId()); + request.setAmount(compactedRequest.amount()); + request.setItemQuantityMultiplier(quantityMultiplier); + requests.add(request); + + farmingXp += GardenServices.visitors().calculateFarmingXp( + baseItems, + GardenServices.visitors().getItemFarmingXpAmount(wantedItem.itemId()), + rarity + ); + copper += GardenServices.visitors().calculateCopper(baseItems, quantityMultiplier, rarity); + } + + long farmingXpReward = Math.round(farmingXp); + int gardenXpReward = GardenServices.visitors().calculateGardenXp(core.getLevel(), rarity); + int bits = GardenServices.visitors().getBitsBase(); + + Map firstVisitOverride = GardenConfigRegistry.getSection(definition, "first_visit_override"); + if (firstVisit && !firstVisitOverride.isEmpty()) { + farmingXpReward = GardenConfigRegistry.getLong(firstVisitOverride, "farming_xp", farmingXpReward); + copper = GardenConfigRegistry.getInt(firstVisitOverride, "copper", copper); + gardenXpReward = GardenConfigRegistry.getInt(firstVisitOverride, "garden_xp", gardenXpReward); + bits = GardenConfigRegistry.getInt(firstVisitOverride, "bits", bits); + } + + Map overrideRewards = GardenConfigRegistry.getSection(definition, "override_rewards"); + List guaranteedRewards = new ArrayList<>(); + if (!overrideRewards.isEmpty()) { + farmingXpReward = GardenConfigRegistry.getLong(overrideRewards, "farming_xp", farmingXpReward); + copper = GardenConfigRegistry.getInt(overrideRewards, "copper", copper); + gardenXpReward = GardenConfigRegistry.getInt(overrideRewards, "garden_xp", gardenXpReward); + bits = GardenConfigRegistry.getInt(overrideRewards, "bits", bits); + guaranteedRewards.addAll(GardenServices.visitors().getConfiguredRewards(overrideRewards, "rewards")); + for (Object reward : GardenConfigRegistry.getList(overrideRewards, "items")) { + GardenData.GardenRewardState rewardState = GardenServices.visitors().parseReward(reward); + if (rewardState != null) { + guaranteedRewards.add(rewardState); + } + } + } + + guaranteedRewards.addAll(GardenServices.visitors().getConfiguredRewards(definition, "guaranteed_rewards")); + + for (Object reward : GardenConfigRegistry.getList(definition, "unique_rewards")) { + GardenData.GardenRewardState rewardState = GardenServices.visitors().parseReward(reward); + if (rewardState == null) { + continue; + } + if (!rewardState.isFirstVisitOnly() || firstVisit) { + guaranteedRewards.add(rewardState); + } + } + + if (firstVisit) { + guaranteedRewards.addAll(GardenServices.visitors().getConfiguredRewards(firstVisitOverride, "rewards")); + } + + List bonusRewards = rollBonusRewards(rarity); + + GardenData.GardenVisitorState state = new GardenData.GardenVisitorState(); + state.setVisitorId(visitorId); + state.setRarity(rarity); + state.setRequests(requests); + state.setFarmingXp(farmingXpReward); + state.setGardenXp(gardenXpReward); + state.setCopper(copper); + state.setBits(bits); + state.setGuaranteedRewards(guaranteedRewards); + state.setBonusRewards(bonusRewards); + state.setArrivedAt(now); + return state; + } + + private static boolean canVisit( + SkyBlockPlayer player, + GardenData.GardenVisitorsData visitors, + GardenData.GardenCoreData core, + Map definition + ) { + String visitorId = GardenConfigRegistry.getString(definition, "id", ""); + if (visitorId.isBlank()) { + return false; + } + boolean alreadyPresent = visitors.getActiveVisitors().stream().anyMatch(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)) + || visitors.getQueuedVisitors().stream().anyMatch(visitor -> visitor.getVisitorId().equalsIgnoreCase(visitorId)); + if (alreadyPresent) { + return false; + } + if (core.getLevel() < GardenConfigRegistry.getInt(definition, "garden_level", 1)) { + return false; + } + + for (GardenVisitorService.VisitorRequirement requirement : GardenServices.visitors().getRequirements(definition)) { + if (!isRequirementMet(player, core, visitorId, requirement)) { + return false; + } + } + return true; + } + + private static boolean isRequirementMet(SkyBlockPlayer player, GardenData.GardenCoreData core, String visitorId, GardenVisitorService.VisitorRequirement requirement) { + if (requirement == null) { + return true; + } + return switch (requirement.type()) { + case "SPOKEN_TO_NPC" -> isTalkRequirementMet(player, visitorId, requirement.key()); + case "GARDEN_LEVEL_AT_LEAST" -> core.getLevel() >= requirement.amount(); + case "SKILL_LEVEL_AT_LEAST" -> switch (requirement.key()) { + case "FARMING" -> player.getSkills().getCurrentLevel(SkillCategories.FARMING) >= requirement.amount(); + case "FISHING" -> player.getSkills().getCurrentLevel(SkillCategories.FISHING) >= requirement.amount(); + default -> false; + }; + case "GARDEN_FLAG", "PROFILE_FLAG", "CUSTOM_PROFILE_FLAG", "ACCESS_FLAG" -> + GardenGuiSupport.personal(player).getVisitorRequirementFlags().contains(requirement.key()) + || GardenGuiSupport.personal(player).getAccessFlags().contains(requirement.key()) + || GardenGuiSupport.core(player).getVisitorRequirementFlags().contains(requirement.key()); + case "GARDEN_COUNTER_AT_LEAST", "PROFILE_COUNTER" -> + ("SERVED_UNIQUE_VISITORS".equals(requirement.key()) && GardenGuiSupport.core(player).getServedUniqueVisitors().size() >= requirement.amount()) + || GardenGuiSupport.personal(player).getVisitorRequirementCounters().getOrDefault(requirement.key(), 0L) >= requirement.amount() + || GardenGuiSupport.core(player).getVisitorRequirementCounters().getOrDefault(requirement.key(), 0L) >= requirement.amount(); + case "ITEM_DONATED" -> GardenGuiSupport.personal(player).getDonatedItems().contains(requirement.key()); + case "ITEM_EXPORTED" -> + GardenGuiSupport.personal(player).getExportedItems().getOrDefault(requirement.key(), 0L) >= requirement.amount(); + case "HAS_UNLOCK" -> GardenGuiSupport.personal(player).getAccessFlags().contains(requirement.key()) + || GardenGuiSupport.core(player).getSkyMartPurchases().contains(requirement.key().toLowerCase()) + || GardenGuiSupport.core(player).getOwnedBarnSkins().contains(requirement.key().toLowerCase()); + case "VISITOR_SERVED" -> + GardenGuiSupport.core(player).getServedUniqueVisitors().contains(requirement.key().toLowerCase()) + || GardenGuiSupport.visitors(player).getServedCounts().getOrDefault(requirement.key().toLowerCase(), 0) >= requirement.amount(); + case "MAYOR_PERK_ACTIVE" -> GardenGuiSupport.personal(player).getAccessFlags().contains(requirement.key()); + default -> false; + }; + } + + private static boolean isTalkRequirementMet(SkyBlockPlayer player, String visitorId, String requirementKey) { + String normalizedNpc = normalizeDialogueFlag(requirementKey); + List spokenFlags = new ArrayList<>(GardenGuiSupport.personal(player).getSpokenNpcFlags()); + return spokenFlags.contains(visitorId.toLowerCase()) + || spokenFlags.contains(normalizedNpc) + || spokenFlags.contains(requirementKey) + || spokenFlags.contains(requirementKey.toLowerCase(java.util.Locale.ROOT)) + || spokenFlags.contains(requirementKey.toUpperCase(java.util.Locale.ROOT)); + } + + private static List resolveWantedItems(Map definition, boolean firstVisit) { + Map firstVisitOverride = firstVisit ? GardenConfigRegistry.getSection(definition, "first_visit_override") : Map.of(); + List configuredWantedItems = firstVisit ? GardenConfigRegistry.getList(firstVisitOverride, "wanted_items") : List.of(); + if (configuredWantedItems.isEmpty()) { + Map requestRules = GardenConfigRegistry.getSection(definition, "request_rules"); + configuredWantedItems = GardenConfigRegistry.getList(requestRules, "items"); + } + if (configuredWantedItems.isEmpty()) { + configuredWantedItems = GardenConfigRegistry.getList(definition, "wanted_items"); + } + if (configuredWantedItems.isEmpty()) { + return List.of(new WantedRequest("WHEAT", 0)); + } + + List resolved = new ArrayList<>(); + for (Object configuredWantedItem : configuredWantedItems) { + if (configuredWantedItem instanceof Map mapRaw) { + @SuppressWarnings("unchecked") + Map itemConfig = (Map) mapRaw; + String itemId = GardenConfigRegistry.getString(itemConfig, "id", "").trim().toUpperCase(); + int fixedAmount = GardenConfigRegistry.getInt(itemConfig, "fixed_amount", 0); + if (!itemId.isBlank()) { + resolved.add(new WantedRequest(itemId, Math.max(0, fixedAmount))); + } + continue; + } + + String entry = String.valueOf(configuredWantedItem).trim(); + if (entry.isBlank()) { + continue; + } + Matcher matcher = ANY_PATTERN.matcher(entry); + if (matcher.matches()) { + int amount = matcher.group(1) == null ? 1 : Integer.parseInt(matcher.group(1)); + List pool = new ArrayList<>(GardenCropRegistry.getVisitorCropPool()); + while (amount > 0 && !pool.isEmpty()) { + String selected = pool.remove(ThreadLocalRandom.current().nextInt(pool.size())); + resolved.add(new WantedRequest(selected, 0)); + amount--; + } + continue; + } + + Matcher fixedMatcher = Pattern.compile("^([\\d,]+)X\\s+(.+)$", Pattern.CASE_INSENSITIVE).matcher(entry); + if (fixedMatcher.matches()) { + int fixedAmount = Integer.parseInt(fixedMatcher.group(1).replace(",", "")); + resolved.add(new WantedRequest(fixedMatcher.group(2).trim().toUpperCase(), fixedAmount)); + continue; + } + + resolved.add(new WantedRequest(entry.toUpperCase(), 0)); + } + if (resolved.isEmpty()) { + return List.of(new WantedRequest("WHEAT", 0)); + } + if (firstVisit && "jerry".equalsIgnoreCase(GardenConfigRegistry.getString(definition, "id", "")) + && resolved.stream().anyMatch(request -> request.itemId().equals("BREAD"))) { + return List.of(new WantedRequest("BREAD", 0)); + } + return resolved; + } + + private static List rollBonusRewards(String rarity) { + List rewards = new ArrayList<>(); + for (Map entry : GardenServices.visitors().getBonusRewards()) { + String minRarity = GardenConfigRegistry.getString(entry, "min_rarity", "UNCOMMON"); + if (!isAtLeastRarity(rarity, minRarity)) { + continue; + } + if (ThreadLocalRandom.current().nextDouble() <= GardenConfigRegistry.getDouble(entry, "chance", 0D)) { + List configuredRewards = GardenServices.visitors().getConfiguredRewards(entry, "rewards"); + if (!configuredRewards.isEmpty()) { + rewards.addAll(configuredRewards); + continue; + } + GardenData.GardenRewardState rewardState = GardenServices.visitors().parseReward(GardenConfigRegistry.getString(entry, "item", "")); + if (rewardState != null) { + rewards.add(rewardState); + } + } + } + return rewards; + } + + private static Map chooseWeighted(List> candidates) { + double totalWeight = candidates.stream() + .mapToDouble(candidate -> GardenServices.visitors().getRarityWeight(GardenConfigRegistry.getString(candidate, "rarity", "UNCOMMON"))) + .sum(); + if (totalWeight <= 0D) { + return candidates.getFirst(); + } + + double roll = ThreadLocalRandom.current().nextDouble(totalWeight); + double cursor = 0D; + for (Map candidate : candidates) { + cursor += GardenServices.visitors().getRarityWeight(GardenConfigRegistry.getString(candidate, "rarity", "UNCOMMON")); + if (roll <= cursor) { + return candidate; + } + } + return candidates.getLast(); + } + + private static boolean isAtLeastRarity(String rarity, String minRarity) { + return RARITY_ORDER.indexOf(rarity.toUpperCase()) >= RARITY_ORDER.indexOf(minRarity.toUpperCase()); + } + + private static boolean isActivelyFarming(GardenData.GardenVisitorsData visitors, long now) { + return now - visitors.getLastFarmingActivityAt() <= 2_500L; + } + + private static long arrivalMillis(GardenData.GardenCoreData core) { + return GardenServices.visitors().getArrivalMinutes(core.getServedUniqueVisitors().size()) * 60_000L; + } + + private static String normalizeDialogueFlag(String value) { + return value.toLowerCase() + .replaceAll("[^a-z0-9]+", "_") + .replaceAll("^_+|_+$", ""); + } + + private record WantedRequest(String itemId, int fixedAmount) { + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorService.java b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorService.java new file mode 100644 index 0000000000..a00bc2f595 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/visitor/GardenVisitorService.java @@ -0,0 +1,634 @@ +package net.swofty.type.garden.visitor; + +import net.swofty.commons.skyblock.item.ItemType; +import net.swofty.type.garden.config.GardenConfigRegistry; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class GardenVisitorService { + private Map config = Map.of(); + private final Map rarityWeights = new HashMap<>(); + private final Map baseItemsByLevel = new HashMap<>(); + private final Map quantityMultipliers = new HashMap<>(); + private final Map requestRarityMultipliers = new HashMap<>(); + private final Map farmingXpRarityMultipliers = new HashMap<>(); + private final Map itemFarmingXpAmounts = new HashMap<>(); + private final Map copperRarityMultipliers = new HashMap<>(); + private final Map> gardenXpByLevel = new HashMap<>(); + private final Map requestItemAliases = new HashMap<>(); + private final Map> requestCompactionProfiles = new HashMap<>(); + private List> bonusRewards = List.of(); + private List> visitors = List.of(); + + public void reload() { + config = GardenConfigRegistry.getConfig("visitors.yml"); + visitors = GardenConfigRegistry.getMapList(config, "registry"); + bonusRewards = GardenConfigRegistry.getMapList(config, "bonus_rewards"); + + rarityWeights.clear(); + rarityWeights.putAll(GardenConfigRegistry.getSection(config, "rarity_weights")); + + quantityMultipliers.clear(); + GardenConfigRegistry.getSection(config, "item_quantity_multipliers") + .forEach((key, value) -> quantityMultipliers.put(normalizeItemKey(key), Double.parseDouble(String.valueOf(value)))); + + requestItemAliases.clear(); + GardenConfigRegistry.getSection(config, "request_item_aliases") + .forEach((key, value) -> requestItemAliases.put(normalizeItemKey(key), normalizeItemKey(String.valueOf(value)))); + + requestRarityMultipliers.clear(); + GardenConfigRegistry.getSection(config, "rarity_request_multipliers") + .forEach((key, value) -> requestRarityMultipliers.put(normalizeItemKey(key), Double.parseDouble(String.valueOf(value)))); + + farmingXpRarityMultipliers.clear(); + GardenConfigRegistry.getSection(config, "farming_xp_rarity_multipliers") + .forEach((key, value) -> farmingXpRarityMultipliers.put(normalizeItemKey(key), Double.parseDouble(String.valueOf(value)))); + + itemFarmingXpAmounts.clear(); + GardenConfigRegistry.getSection(config, "item_farming_xp_amounts") + .forEach((key, value) -> itemFarmingXpAmounts.put(normalizeItemKey(key), Double.parseDouble(String.valueOf(value)))); + + copperRarityMultipliers.clear(); + GardenConfigRegistry.getSection(config, "copper_rarity_multipliers") + .forEach((key, value) -> copperRarityMultipliers.put(normalizeItemKey(key), Double.parseDouble(String.valueOf(value)))); + + baseItemsByLevel.clear(); + for (Map entry : GardenConfigRegistry.getMapList(config, "base_item_ranges")) { + int level = GardenConfigRegistry.getInt(entry, "level", 1); + baseItemsByLevel.put(level, new int[]{ + GardenConfigRegistry.getInt(entry, "min", 500), + GardenConfigRegistry.getInt(entry, "max", 1000) + }); + } + + gardenXpByLevel.clear(); + for (Map entry : GardenConfigRegistry.getMapList(config, "garden_xp_by_level")) { + int level = GardenConfigRegistry.getInt(entry, "level", 1); + Map rarityMap = new HashMap<>(); + rarityMap.put("UNCOMMON", GardenConfigRegistry.getInt(entry, "uncommon", 2)); + rarityMap.put("RARE", GardenConfigRegistry.getInt(entry, "rare", rarityMap.get("UNCOMMON"))); + rarityMap.put("LEGENDARY", GardenConfigRegistry.getInt(entry, "legendary", rarityMap.get("UNCOMMON"))); + rarityMap.put("MYTHIC", GardenConfigRegistry.getInt(entry, "mythic", rarityMap.get("LEGENDARY"))); + rarityMap.put("SPECIAL", GardenConfigRegistry.getInt(entry, "special", rarityMap.get("LEGENDARY"))); + gardenXpByLevel.put(level, rarityMap); + } + + requestCompactionProfiles.clear(); + for (Map.Entry entry : GardenConfigRegistry.getSection(config, "request_compaction").entrySet()) { + if (!(entry.getValue() instanceof Map profileSectionRaw)) { + continue; + } + @SuppressWarnings("unchecked") + Map profileSection = (Map) profileSectionRaw; + List profiles = new ArrayList<>(); + for (Map profileConfig : GardenConfigRegistry.getMapList(profileSection, "profiles")) { + List stages = GardenConfigRegistry.getMapList(profileConfig, "stages").stream() + .map(stageConfig -> new CompactionStage( + normalizeItemKey(GardenConfigRegistry.getString(stageConfig, "item", "")), + Math.max(1, GardenConfigRegistry.getInt(stageConfig, "raw_cost", 1)) + )) + .filter(stage -> !stage.itemId().isBlank()) + .sorted(Comparator.comparingInt(CompactionStage::rawCost)) + .toList(); + if (stages.isEmpty()) { + continue; + } + profiles.add(new CompactionProfile( + normalizeItemKey(GardenConfigRegistry.getString(profileConfig, "id", entry.getKey())), + GardenConfigRegistry.getDouble(profileConfig, "weight", 1D), + stages + )); + } + if (!profiles.isEmpty()) { + requestCompactionProfiles.put(normalizeItemKey(entry.getKey()), List.copyOf(profiles)); + } + } + } + + public int getArrivalMinutes(int servedUniqueVisitors) { + Map arrivalMinutes = GardenConfigRegistry.getSection(config, "arrival_minutes"); + if (servedUniqueVisitors >= 100) { + return GardenConfigRegistry.getInt(arrivalMinutes, "one_hundred_unique", 9); + } + if (servedUniqueVisitors >= 50) { + return GardenConfigRegistry.getInt(arrivalMinutes, "fifty_unique", 12); + } + return GardenConfigRegistry.getInt(arrivalMinutes, "base", 15); + } + + public int[] getBaseItemsRange(int gardenLevel) { + return baseItemsByLevel.getOrDefault(gardenLevel, baseItemsByLevel.getOrDefault(1, new int[]{500, 1000})); + } + + public double getQuantityMultiplier(String itemId) { + return quantityMultipliers.getOrDefault(normalizeItemKey(itemId), 1D); + } + + public String resolveRequestedItemKey(String itemId) { + String normalized = normalizeItemKey(itemId); + return requestItemAliases.getOrDefault(normalized, normalized); + } + + public String getRepresentativeRequestItem(String wantedItem) { + String resolvedWantedItem = resolveRequestedItemKey(wantedItem); + List profiles = requestCompactionProfiles.get(resolvedWantedItem); + if (profiles == null || profiles.isEmpty()) { + return resolvedWantedItem; + } + List stages = profiles.getFirst().stages(); + return stages.isEmpty() ? resolvedWantedItem : stages.getFirst().itemId(); + } + + public CompactedRequest compactRequest(String wantedItem, int rawAmount) { + int sanitizedRawAmount = Math.max(1, rawAmount); + String resolvedWantedItem = resolveRequestedItemKey(wantedItem); + List profiles = requestCompactionProfiles.get(resolvedWantedItem); + if (profiles == null || profiles.isEmpty()) { + return new CompactedRequest(resolvedWantedItem, sanitizedRawAmount, sanitizedRawAmount); + } + + CompactionProfile profile = chooseCompactionProfile(profiles); + CompactionStage selectedStage = profile.stages().getFirst(); + for (CompactionStage stage : profile.stages()) { + if (stage.rawCost() <= sanitizedRawAmount) { + selectedStage = stage; + } else { + break; + } + } + + int compactedAmount = Math.max(1, sanitizedRawAmount / selectedStage.rawCost()); + int remainingRaw = sanitizedRawAmount % selectedStage.rawCost(); + if (selectedStage.rawCost() > 1 + && remainingRaw > 0 + && remainingRaw < sanitizedRawAmount * 0.10D) { + compactedAmount++; + } + return new CompactedRequest(selectedStage.itemId(), compactedAmount, sanitizedRawAmount); + } + + public double getRequestMultiplier(String rarity) { + return requestRarityMultipliers.getOrDefault(normalizeItemKey(rarity), 1D); + } + + public double getItemFarmingXpAmount(String itemId) { + String normalized = normalizeItemKey(itemId); + return itemFarmingXpAmounts.getOrDefault(normalized, getQuantityMultiplier(normalized)); + } + + public double calculateFarmingXp(int baseItems, double itemFarmingXp, String rarity) { + return baseItems * itemFarmingXp * farmingXpRarityMultipliers.getOrDefault(normalizeItemKey(rarity), 0.05D); + } + + public int calculateGardenXp(int gardenLevel, String rarity) { + Map rarityMap = gardenXpByLevel.getOrDefault(gardenLevel, gardenXpByLevel.getOrDefault(1, Map.of())); + return rarityMap.getOrDefault(normalizeItemKey(rarity), rarityMap.getOrDefault("UNCOMMON", 2)); + } + + public int calculateCopper(int baseItems, double itemQuantityMultiplier, String rarity) { + double copper = (((double) baseItems / 2000D) / itemQuantityMultiplier) + * copperRarityMultipliers.getOrDefault(normalizeItemKey(rarity), 1D) + * 1.3D; + return Math.max(2, (int) Math.floor(copper)); + } + + public double getRarityWeight(String rarity) { + Object weight = rarityWeights.get(normalizeItemKey(rarity)); + return weight == null ? 0 : Double.parseDouble(String.valueOf(weight)); + } + + public int getBitsBase() { + return GardenConfigRegistry.getInt(config, "bits_base", 5); + } + + public int getMaxVisibleVisitors() { + return GardenConfigRegistry.getInt(config, "max_visible_visitors", 5); + } + + public int getMaxQueuedVisitors() { + return GardenConfigRegistry.getInt(config, "max_queued_visitors", 1); + } + + public int getExpectedUniqueVisitors() { + return GardenConfigRegistry.getInt(config, "expected_unique_visitors", 137); + } + + public int getFarmingSpeedupMultiplier() { + return GardenConfigRegistry.getInt(config, "farming_speedup_multiplier", 3); + } + + public List> getVisitors() { + return visitors; + } + + public List> getBonusRewards() { + return bonusRewards; + } + + public List getConfiguredRewards(Map root, String key) { + List rewards = new ArrayList<>(); + for (Object rawReward : GardenConfigRegistry.getList(root, key)) { + GardenData.GardenRewardState rewardState = parseReward(rawReward); + if (rewardState != null) { + rewards.add(rewardState); + } + } + return rewards; + } + + public GardenData.GardenRewardState parseReward(Object rawReward) { + if (rawReward == null) { + return null; + } + if (rawReward instanceof GardenData.GardenRewardState rewardState) { + return rewardState; + } + if (rawReward instanceof Map rewardMap) { + return parseRewardMap(rewardMap); + } + return parseLegacyReward(String.valueOf(rawReward)); + } + + public List getRequirements(Map definition) { + List requirements = new ArrayList<>(); + for (Object rawRequirement : GardenConfigRegistry.getList(definition, "requirements")) { + VisitorRequirement requirement = parseRequirement(rawRequirement); + if (requirement != null) { + requirements.add(requirement); + } + } + return requirements; + } + + public VisitorRequirement parseRequirement(Object rawRequirement) { + if (rawRequirement == null) { + return null; + } + if (rawRequirement instanceof Map requirementMapRaw) { + Map requirementMap = castMap(requirementMapRaw); + String type = normalizeItemKey(GardenConfigRegistry.getString(requirementMap, "type", "")); + String key = normalizeItemKey(GardenConfigRegistry.getString(requirementMap, "key", "")); + long amount = GardenConfigRegistry.getLong(requirementMap, "amount", 0L); + String secondaryKey = normalizeItemKey(GardenConfigRegistry.getString(requirementMap, "secondary_key", "")); + String display = GardenConfigRegistry.getString(requirementMap, "display", ""); + return new VisitorRequirement(type, key, amount, secondaryKey, display); + } + + String requirement = String.valueOf(rawRequirement).trim(); + if (requirement.isBlank()) { + return null; + } + if (requirement.regionMatches(true, 0, "Talk to", 0, "Talk to".length())) { + String normalizedNpc = normalizeItemKey(requirement + .replaceFirst("(?i)^Talk to\\s+", "") + .replaceFirst("(?i)^the\\s+", "") + .replaceFirst("(?i)^this\\s+", "")); + return new VisitorRequirement("SPOKEN_TO_NPC", normalizedNpc, 1L, "", requirement); + } + if (requirement.regionMatches(true, 0, "Garden ", 0, "Garden ".length())) { + return new VisitorRequirement("GARDEN_LEVEL_AT_LEAST", "", parseTrailingRoman(requirement), "", requirement); + } + if (requirement.regionMatches(true, 0, "Farming ", 0, "Farming ".length())) { + return new VisitorRequirement("SKILL_LEVEL_AT_LEAST", "FARMING", parseTrailingRoman(requirement), "", requirement); + } + if (requirement.regionMatches(true, 0, "Fishing ", 0, "Fishing ".length())) { + return new VisitorRequirement("SKILL_LEVEL_AT_LEAST", "FISHING", parseTrailingRoman(requirement), "", requirement); + } + if (requirement.regionMatches(true, 0, "Donate ", 0, "Donate ".length())) { + return new VisitorRequirement("ITEM_DONATED", normalizeItemKey(requirement.replaceFirst("(?i)^Donate\\s+", "")), 1L, "", requirement); + } + if (requirement.regionMatches(true, 0, "Save ", 0, "Save ".length())) { + return new VisitorRequirement("PROFILE_FLAG", normalizeItemKey(requirement), 1L, "", requirement); + } + if (requirement.regionMatches(true, 0, "Export ", 0, "Export ".length())) { + String exportRequirement = requirement.replaceFirst("(?i)^Export\\s+", ""); + String[] tokens = exportRequirement.split("x\\s+", 2); + if (tokens.length == 2) { + long amount = parseLongValue(tokens[0].replace(",", ""), 0L); + return new VisitorRequirement("ITEM_EXPORTED", normalizeItemKey(tokens[1]), amount, "", requirement); + } + } + if (requirement.contains("Blooming Business")) { + return new VisitorRequirement("MAYOR_PERK_ACTIVE", "BLOOMING_BUSINESS", 1L, "", requirement); + } + return new VisitorRequirement("PROFILE_FLAG", normalizeItemKey(requirement), 1L, "", requirement); + } + + public String renderRequirement(VisitorRequirement requirement) { + if (requirement == null) { + return "Requirement data unavailable"; + } + if (!requirement.display().isBlank()) { + return requirement.display(); + } + return switch (requirement.type()) { + case "GARDEN_LEVEL_AT_LEAST" -> "Garden " + toRoman((int) Math.max(1L, requirement.amount())); + case "SKILL_LEVEL_AT_LEAST" -> + capitalizeWords(requirement.key()) + " " + toRoman((int) Math.max(1L, requirement.amount())); + case "SPOKEN_TO_NPC" -> "Talk to " + capitalizeWords(requirement.key()); + case "ITEM_DONATED" -> "Donate " + capitalizeWords(requirement.key()); + case "ITEM_EXPORTED" -> "Export " + requirement.amount() + "x " + capitalizeWords(requirement.key()); + case "MAYOR_PERK_ACTIVE" -> "Blooming Business"; + default -> capitalizeWords(requirement.key()); + }; + } + + public String describeReward(GardenData.GardenRewardState reward) { + if (reward == null) { + return ""; + } + String displayOverride = reward.getDisplayOverride(); + if (displayOverride != null && !displayOverride.isBlank()) { + return displayOverride; + } + long amount = reward.getAmount() > 0 ? reward.getAmount() : reward.getMin(); + return switch (normalizeItemKey(reward.getType())) { + case "ITEM" -> "+" + amount + "x " + capitalizeWords(reward.getKey()); + case "BITS" -> "+" + amount + " Bits"; + case "COPPER" -> "+" + amount + " Copper"; + case "FARMING_XP" -> "+" + amount + " Farming XP"; + case "GARDEN_XP" -> "+" + amount + " Garden XP"; + case "JACOBS_TICKET" -> "+" + amount + " Jacob's Ticket" + (amount == 1 ? "" : "s"); + case "BARN_SKIN_UNLOCK" -> capitalizeWords(reward.getKey()) + " Barn Skin"; + case "GREENHOUSE_UNLOCK" -> "Greenhouse Unlock"; + case "SKYMART_UNLOCK" -> "SkyMart Unlock: " + capitalizeWords(reward.getKey()); + case "POWDER" -> "+" + amount + " " + capitalizeWords(reward.getKey()) + " Powder"; + case "ESSENCE" -> "+" + amount + " " + capitalizeWords(reward.getKey()) + " Essence"; + case "PELTS" -> "+" + amount + " Pelt" + (amount == 1 ? "" : "s"); + case "FAIRY_SOUL" -> "+" + amount + " Fairy Soul"; + case "ACCESS_FLAG", "PROFILE_FLAG", "TUTORIAL_FLAG" -> capitalizeWords(reward.getKey()); + case "RANDOM_POOL" -> + "Random " + capitalizeWords(reward.getPoolId().isBlank() ? reward.getKey() : reward.getPoolId()); + case "LEGACY" -> capitalizeWords(reward.getKey()); + default -> (amount > 0 ? "+" + amount + " " : "") + capitalizeWords(reward.getKey()); + }; + } + + public Map getVisitor(String id) { + return visitors.stream() + .filter(entry -> GardenConfigRegistry.getString(entry, "id", "").equalsIgnoreCase(id)) + .findFirst() + .orElse(Map.of()); + } + + public String getEntityKind(Map visitor) { + return normalizeItemKey(GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "kind", "HUMAN")); + } + + public String getEntityProfession(Map visitor) { + return normalizeItemKey(GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "profession", "NONE")); + } + + public String getEntityType(Map visitor) { + return normalizeItemKey(GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "type", "PLAINS")); + } + + public String getEntityTexture(Map visitor) { + return GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "texture", ""); + } + + public String getEntitySignature(Map visitor) { + return GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "signature", ""); + } + + public String getIconItem(Map visitor) { + return normalizeItemKey(GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "icon_item", "")); + } + + public String getIconHeadTexture(Map visitor) { + return GardenConfigRegistry.getString(GardenConfigRegistry.getSection(visitor, "entity"), "icon_head_texture", ""); + } + + private CompactionProfile chooseCompactionProfile(List profiles) { + double totalWeight = profiles.stream() + .mapToDouble(profile -> Math.max(0D, profile.weight())) + .sum(); + if (totalWeight <= 0D) { + return profiles.getFirst(); + } + + double roll = ThreadLocalRandom.current().nextDouble(totalWeight); + double cursor = 0D; + for (CompactionProfile profile : profiles) { + cursor += Math.max(0D, profile.weight()); + if (roll <= cursor) { + return profile; + } + } + return profiles.getLast(); + } + + private String normalizeItemKey(String key) { + if (key == null) { + return ""; + } + return key.trim().replace(' ', '_').toUpperCase(); + } + + private long parseLong(String[] tokens, int index, long defaultValue) { + if (tokens.length <= index) { + return defaultValue; + } + try { + return Long.parseLong(tokens[index]); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + public record CompactedRequest(String itemId, int amount, int rawAmount) { + } + + public record VisitorRequirement(String type, String key, long amount, String secondaryKey, String display) { + } + + private record CompactionProfile(String id, double weight, List stages) { + } + + private record CompactionStage(String itemId, int rawCost) { + } + + private GardenData.GardenRewardState parseRewardMap(Map rewardMapRaw) { + Map rewardMap = castMap(rewardMapRaw); + GardenData.GardenRewardState rewardState = new GardenData.GardenRewardState(); + rewardState.setType(normalizeItemKey(GardenConfigRegistry.getString(rewardMap, "type", ""))); + rewardState.setKey(normalizeItemKey(GardenConfigRegistry.getString(rewardMap, "key", ""))); + rewardState.setAmount(GardenConfigRegistry.getLong(rewardMap, "amount", 0L)); + rewardState.setMin(GardenConfigRegistry.getLong(rewardMap, "min", 0L)); + rewardState.setMax(GardenConfigRegistry.getLong(rewardMap, "max", 0L)); + rewardState.setPoolId(normalizeItemKey(GardenConfigRegistry.getString(rewardMap, "pool_id", ""))); + rewardState.setFirstVisitOnly(GardenConfigRegistry.getBoolean(rewardMap, "first_visit_only", false)); + rewardState.setDisplayOverride(GardenConfigRegistry.getString(rewardMap, "display", "")); + + Object metadataRaw = rewardMap.get("metadata"); + if (metadataRaw instanceof Map metadataMapRaw) { + Map metadata = new LinkedHashMap<>(); + metadataMapRaw.forEach((metadataKey, metadataValue) -> metadata.put( + String.valueOf(metadataKey), + String.valueOf(metadataValue) + )); + rewardState.setMetadata(metadata); + } + return rewardState; + } + + private GardenData.GardenRewardState parseLegacyReward(String rawReward) { + if (rawReward == null || rawReward.isBlank()) { + return null; + } + + String trimmed = rawReward.trim(); + boolean firstVisitOnly = trimmed.toUpperCase().endsWith("_ON_FIRST_VISIT"); + if (firstVisitOnly) { + trimmed = trimmed.substring(0, trimmed.length() - "_ON_FIRST_VISIT".length()); + } + + String[] tokens = trimmed.split(":", 3); + String head = normalizeItemKey(tokens[0]); + GardenData.GardenRewardState rewardState = new GardenData.GardenRewardState(); + rewardState.setFirstVisitOnly(firstVisitOnly); + + if (tokens.length == 1) { + if (ItemType.get(head) != null || "GREENHOUSE_BLUEPRINT".equals(head)) { + rewardState.setType("ITEM"); + rewardState.setKey(head); + rewardState.setAmount(1L); + return rewardState; + } + if ("JACOBS_TICKET".equals(head)) { + rewardState.setType("JACOBS_TICKET"); + rewardState.setKey(head); + rewardState.setAmount(1L); + return rewardState; + } + rewardState.setType("PROFILE_FLAG"); + rewardState.setKey(head); + rewardState.setAmount(1L); + return rewardState; + } + + switch (head) { + case "ITEM" -> { + rewardState.setType("ITEM"); + rewardState.setKey(normalizeItemKey(tokens.length >= 2 ? tokens[1] : "")); + rewardState.setAmount(parseLong(tokens, 2, 1L)); + } + case "BITS", "COPPER", "FARMING_XP", "GARDEN_XP", "JACOBS_TICKET" -> { + rewardState.setType(head); + rewardState.setKey(head); + rewardState.setAmount(parseLong(tokens, 1, "JACOBS_TICKET".equals(head) ? 1L : 0L)); + } + case "BARN_SKIN" -> { + rewardState.setType("BARN_SKIN_UNLOCK"); + rewardState.setKey(normalizeItemKey(tokens[1])); + rewardState.setAmount(1L); + } + case "SKYMART_UNLOCK" -> { + rewardState.setType("SKYMART_UNLOCK"); + rewardState.setKey(normalizeItemKey(tokens[1])); + rewardState.setAmount(1L); + } + case "TUTORIAL_FLAG" -> { + rewardState.setType("TUTORIAL_FLAG"); + rewardState.setKey(normalizeItemKey(tokens[1])); + rewardState.setAmount(1L); + } + default -> { + if (ItemType.get(head) != null) { + rewardState.setType("ITEM"); + rewardState.setKey(head); + rewardState.setAmount(parseLong(tokens, 1, 1L)); + } else { + rewardState.setType("PROFILE_FLAG"); + rewardState.setKey(normalizeItemKey(trimmed)); + rewardState.setAmount(1L); + } + } + } + return rewardState; + } + + @SuppressWarnings("unchecked") + private Map castMap(Map rawMap) { + return (Map) rawMap; + } + + private long parseLongValue(String rawValue, long defaultValue) { + try { + return Long.parseLong(rawValue); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + private int parseTrailingRoman(String requirement) { + String roman = requirement.replaceAll("^.*\\s+", "").trim(); + return switch (roman.toUpperCase()) { + case "I" -> 1; + case "II" -> 2; + case "III" -> 3; + case "IV" -> 4; + case "V" -> 5; + case "VI" -> 6; + case "VII" -> 7; + case "VIII" -> 8; + case "IX" -> 9; + case "X" -> 10; + case "XI" -> 11; + case "XII" -> 12; + case "XIII" -> 13; + case "XIV" -> 14; + case "XV" -> 15; + default -> (int) parseLongValue(roman, 1L); + }; + } + + private String toRoman(int value) { + return switch (value) { + case 1 -> "I"; + case 2 -> "II"; + case 3 -> "III"; + case 4 -> "IV"; + case 5 -> "V"; + case 6 -> "VI"; + case 7 -> "VII"; + case 8 -> "VIII"; + case 9 -> "IX"; + case 10 -> "X"; + case 11 -> "XI"; + case 12 -> "XII"; + case 13 -> "XIII"; + case 14 -> "XIV"; + case 15 -> "XV"; + default -> String.valueOf(value); + }; + } + + private String capitalizeWords(String key) { + if (key == null || key.isBlank()) { + return ""; + } + String[] parts = key.toLowerCase().split("_"); + StringBuilder builder = new StringBuilder(); + for (int index = 0; index < parts.length; index++) { + if (parts[index].isBlank()) { + continue; + } + if (builder.length() > 0) { + builder.append(' '); + } + builder.append(Character.toUpperCase(parts[index].charAt(0))); + if (parts[index].length() > 1) { + builder.append(parts[index].substring(1)); + } + } + return builder.toString(); + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/world/GardenAssetRegistry.java b/type.garden/src/main/java/net/swofty/type/garden/world/GardenAssetRegistry.java new file mode 100644 index 0000000000..2fd4217af1 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/world/GardenAssetRegistry.java @@ -0,0 +1,42 @@ +package net.swofty.type.garden.world; + +import net.hollowcube.schem.Schematic; +import net.swofty.type.garden.config.GardenBarnSkinDefinition; +import net.swofty.type.garden.config.GardenConfigRegistry; +import org.tinylog.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public final class GardenAssetRegistry { + private static final Path GARDEN_SEED = Path.of("./configuration/skyblock/islands/garden_default.polar"); + private static final Map BARN_SCHEMATICS = new HashMap<>(); + + private GardenAssetRegistry() { + } + + public static Path getGardenSeed() { + return GARDEN_SEED; + } + + public static Path getBarnSkinSchematicPath(String fileName) { + return GardenConfigRegistry.CONFIG_DIR.resolve(fileName); + } + + public static Schematic getBarnSkinSchematic(GardenBarnSkinDefinition definition) { + return BARN_SCHEMATICS.computeIfAbsent(definition.id(), ignored -> loadSchematic(definition)); + } + + private static Schematic loadSchematic(GardenBarnSkinDefinition definition) { + Path file = getBarnSkinSchematicPath(definition.schematicFile()); + try { + return new LitematicaSchematicReader().read(Files.readAllBytes(file)); + } catch (IOException e) { + Logger.error(e, "Failed to load barn skin schematic {}", file); + throw new RuntimeException("Failed to load barn skin schematic " + definition.id(), e); + } + } +} diff --git a/type.garden/src/main/java/net/swofty/type/garden/world/LitematicaSchematicReader.java b/type.garden/src/main/java/net/swofty/type/garden/world/LitematicaSchematicReader.java new file mode 100644 index 0000000000..73fce0741d --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/world/LitematicaSchematicReader.java @@ -0,0 +1,290 @@ +package net.swofty.type.garden.world; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.hollowcube.schem.BlockEntityData; +import net.hollowcube.schem.Schematic; +import net.hollowcube.schem.SpongeSchematic; +import net.hollowcube.schem.reader.SchematicReader; +import net.hollowcube.schem.util.BlockConsumer; +import net.hollowcube.schem.util.CoordinateUtil; +import net.hollowcube.schem.util.GameDataProvider; +import net.hollowcube.schem.util.Rotation; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.ByteArrayBinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.LongBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.instance.block.Block; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NonNull; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.text.MessageFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static net.hollowcube.schem.util.CoordinateUtil.blockIndex; + +/** + * A modified version of Schem library's LitematicaSchematicReader which was licensed under + * MIT License + */ +final class LitematicaSchematicReader implements SchematicReader { + private final GameDataProvider gameData = GameDataProvider.provider(); + + @Override + public @NonNull Schematic read(byte @NonNull [] data) { + try { + return read(BinaryTagIO.reader().readNamed( + new ByteArrayInputStream(data), + BinaryTagIO.Compression.GZIP + )); + } catch (IOException e) { + throw new RuntimeException("failed to read root compound", e); + } + } + + @ApiStatus.Internal + public Schematic read(Map.Entry rootPair) { + assertTrue("".equals(rootPair.getKey()), "root tag must be empty, was: '{0}'", rootPair.getKey()); + var root = rootPair.getValue(); + var dataVersion = getRequired(root, "MinecraftDataVersion", BinaryTagTypes.INT).value(); + var version = getRequired(root, "Version", BinaryTagTypes.INT).value(); + var subVersion = getRequired(root, "SubVersion", BinaryTagTypes.INT).value(); + // flexible version check - ARI + assertTrue(version == 6 || version == 7, "unsupported version (only 6 and 7 are supported): {0}.{1}", version, subVersion); + + var metadata = getRequired(root, "Metadata", BinaryTagTypes.COMPOUND); + var enclosingSize = getRequiredVec3(metadata, "EnclosingSize"); + assertTrue(enclosingSize.blockX() > 0, "invalid enclosing width: {0}", enclosingSize.blockX()); + assertTrue(enclosingSize.blockY() > 0, "invalid enclosing height: {0}", enclosingSize.blockY()); + assertTrue(enclosingSize.blockZ() > 0, "invalid enclosing length: {0}", enclosingSize.blockZ()); + + var regions = new HashMap(); + for (var regionPair : getRequired(root, "Regions", BinaryTagTypes.COMPOUND)) { + var region = loadRegion(dataVersion, (CompoundBinaryTag) regionPair.getValue()); + regions.put(regionPair.getKey(), region); + } + + return new RegionedLitematicaSchematic(metadata, enclosingSize, regions); + } + + private Schematic loadRegion(int dataVersion, CompoundBinaryTag region) { + var rawPos = getRequiredVec3(region, "Position"); + var rawSize = getRequiredVec3(region, "Size") + .withX(x -> x >= 0 ? x - 1 : x + 1) + .withY(y -> y >= 0 ? y - 1 : y + 1) + .withZ(z -> z >= 0 ? z - 1 : z + 1); + var relativeEnd = rawSize.add(rawPos); + var absoluteMin = CoordinateUtil.min(rawPos, relativeEnd); + var absoluteMax = CoordinateUtil.max(rawPos, relativeEnd); + var size = absoluteMax.sub(absoluteMin).add(1); + + // === Block Palette === + var paletteList = getRequired(region, "BlockStatePalette", BinaryTagTypes.LIST); + var blockPalette = new Block[paletteList.size()]; + for (int i = 0; i < paletteList.size(); i++) + blockPalette[i] = readBlockState(paletteList.getCompound(i)); + + // Litematica doesn't include the block entity id in TileEntities, so we have to look up + // the block IDs from the palette while creating blockData to get the block id. + // We can use the Minestom registry to get the associated block entity and store that too. + Int2ObjectMap blockEntityData = new Int2ObjectOpenHashMap<>(); + for (var blockEntityTag : region.getList("TileEntities", BinaryTagTypes.COMPOUND)) { + var blockEntity = (CompoundBinaryTag) blockEntityTag; + var pos = getRequiredVec3(blockEntity, ""); + var data = blockEntity.remove("x").remove("y").remove("z"); + + var index = (int) (pos.blockX() + pos.blockZ() * size.x() + pos.blockY() * size.x() * size.z()); + blockEntityData.put(index, data); + } + + var blockEntities = new Int2ObjectArrayMap(); + var packedBlocks = getRequired(region, "BlockStates", BinaryTagTypes.LONG_ARRAY).value(); + var unpackedBlocks = new int[size.blockX() * size.blockY() * size.blockZ()]; + var bitsPerEntry = Math.max(2, Integer.SIZE - Integer.numberOfLeadingZeros(blockPalette.length - 1)); + unpackPaletteTight(unpackedBlocks, packedBlocks, bitsPerEntry); + var blockData = ByteArrayBinaryTag.byteArrayBinaryTag(NetworkBuffer.makeArray(buffer -> { + for (int index = 0; index < unpackedBlocks.length; index++) { + var paletteIndex = unpackedBlocks[index]; + buffer.write(NetworkBuffer.VAR_INT, paletteIndex); + + // Try to find block entity for this block + var blockEntityType = blockPalette[paletteIndex].registry().blockEntityType(); + if (blockEntityType != null) { + var blockEntity = blockEntityData.getOrDefault(index, CompoundBinaryTag.empty()); + var blockPosition = new Vec( + index % size.x(), + (index / size.x()) % size.z(), + index / (size.x() * size.z()) + ); + if (dataVersion != gameData.dataVersion()) { + blockEntity = gameData.upgradeBlockEntity(dataVersion, gameData.dataVersion(), blockEntityType.name(), blockEntity); + } + blockEntities.put(blockIndex(size, blockPosition), new BlockEntityData(blockEntityType.name(), blockPosition, blockEntity)); + } + } + })); + + // === Entities === + // disable entities - ARI + /*var entities = new ArrayList(); + for (var entityTag : region.getList("Entities", BinaryTagTypes.COMPOUND)) { + var entity = (CompoundBinaryTag) entityTag; + if (dataVersion != gameData.dataVersion()) + entity = gameData.upgradeEntity(dataVersion, gameData.dataVersion(), entity); + entities.add(entity); + }*/ + + return new SpongeSchematic( + CompoundBinaryTag.empty(), size, absoluteMin, + List.of(blockPalette), blockData, + List.of(), SpongeSchematic.EMPTY_BYTE_ARRAY, + blockEntities, List.of() + ); + } + + private static void assertTrue(boolean condition, String message, Object... args) { + if (condition) { + return; + } + throw new IllegalArgumentException(MessageFormat.format(message, args)); + } + + private static T getRequired(CompoundBinaryTag tag, String key, BinaryTagType type) { + var value = tag.get(key); + if (value == null) { + throw new IllegalArgumentException("missing required field '" + key + "'"); + } + if (value.type() != type) { + throw new IllegalArgumentException("expected field '" + key + "' to be a " + type); + } + @SuppressWarnings("unchecked") + var castValue = (T) value; + return castValue; + } + + private static Point getRequiredVec3(CompoundBinaryTag tag, String key) { + var vec = key.isEmpty() ? tag : getRequired(tag, key, BinaryTagTypes.COMPOUND); + var x = getRequired(vec, "x", BinaryTagTypes.INT).value(); + var y = getRequired(vec, "y", BinaryTagTypes.INT).value(); + var z = getRequired(vec, "z", BinaryTagTypes.INT).value(); + return new Vec(x, y, z); + } + + private static Block readBlockState(CompoundBinaryTag tag) { + var name = getRequired(tag, "Name", BinaryTagTypes.STRING).value(); + var block = Block.fromKey(name); + assertTrue(block != null, "unknown block: {0}", name); + + var propsTag = tag.getCompound("Properties"); + if (propsTag.isEmpty()) { + return block; + } + + var properties = new HashMap(); + for (var entry : propsTag) { + assertTrue(entry.getValue().type() == BinaryTagTypes.STRING, "expected property value to be a string"); + properties.put(entry.getKey(), ((StringBinaryTag) entry.getValue()).value()); + } + + try { + return block.withProperties(properties); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("failed to parse block properties", e); + } + } + + private static void unpackPaletteTight(int[] out, long[] in, int bitsPerEntry) { + assert in.length != 0 : "unpack input array is zero"; + + long maxEntryValue = (1L << bitsPerEntry) - 1L; + for (int i = 0; i < out.length; i++) { + long startOffset = i * (long) bitsPerEntry; + int startArrIndex = (int) (startOffset >> 6); + int endArrIndex = (int) (((i + 1L) * bitsPerEntry - 1L) >> 6); + int subIndex = (int) (startOffset & 0x3F); + + if (startArrIndex == endArrIndex) { + out[i] = (int) ((in[startArrIndex] >>> subIndex) & maxEntryValue); + } else { + out[i] = (int) ((in[startArrIndex] >>> subIndex | in[endArrIndex] << (64 - subIndex)) & maxEntryValue); + } + } + } + + record RegionedLitematicaSchematic( + CompoundBinaryTag metadata, + Point size, + Map regions + ) implements Schematic { + @Override + public void forEachBlock(@NonNull Rotation rotation, @NonNull BlockConsumer consumer) { + for (var region : regions.values()) { + region.forEachBlock(rotation, consumer); + } + } + + @Override + public String name() { + var name = metadata.get("Name"); + if (name != null && name.type() == BinaryTagTypes.STRING) { + return ((StringBinaryTag) name).value(); + } + return null; + } + + @Override + public String author() { + var author = metadata.get("Author"); + if (author != null && author.type() == BinaryTagTypes.STRING) { + return ((StringBinaryTag) author).value(); + } + return null; + } + + @Override + public Instant createdAt() { + var createdAt = metadata.get("TimeCreated"); + if (createdAt != null && createdAt.type() == BinaryTagTypes.LONG) { + return Instant.ofEpochMilli(((LongBinaryTag) createdAt).value()); + } + return null; + } + + @Override + public boolean hasBlockData() { + return regions.values().stream().anyMatch(Schematic::hasBlockData); + } + + @Override + public @NonNull List blockEntities() { + var blockEntities = new ArrayList(); + for (var region : regions.values()) { + blockEntities.addAll(region.blockEntities()); + } + return blockEntities; + } + + @Override + public @NonNull List entities() { + var entities = new ArrayList(); + for (var region : regions.values()) { + entities.addAll(region.entities()); + } + return entities; + } + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/HypixelConst.java b/type.generic/src/main/java/net/swofty/type/generic/HypixelConst.java index 107e71c4c1..5e99505779 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/HypixelConst.java +++ b/type.generic/src/main/java/net/swofty/type/generic/HypixelConst.java @@ -50,4 +50,8 @@ public class HypixelConst { public static boolean isIslandServer() { return typeLoader.getType() == ServerType.SKYBLOCK_ISLAND; } -} \ No newline at end of file + + public static boolean isGarden() { + return typeLoader.getType() == ServerType.SKYBLOCK_GARDEN; + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/command/HypixelCommand.java b/type.generic/src/main/java/net/swofty/type/generic/command/HypixelCommand.java index c0f9df2fef..c1255066ad 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/command/HypixelCommand.java +++ b/type.generic/src/main/java/net/swofty/type/generic/command/HypixelCommand.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; public abstract class HypixelCommand { @Getter @@ -52,8 +53,12 @@ protected HypixelCommand() { public boolean permissionCheck(CommandSender sender) { HypixelPlayer player = (HypixelPlayer) sender; - HypixelDataHandler dataHandler = player.getDataHandler(); - boolean passes = dataHandler.get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(params.permission()); + Optional dataHandler = player.getOptionalDataHandler(); + if (dataHandler.isEmpty()) { + player.sendMessage("§cYour player data is unavailable right now. Please reconnect."); + return false; + } + boolean passes = dataHandler.get().get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(params.permission()); if (!passes) { player.sendMessage("§cYou do not have permission to use this command."); @@ -67,19 +72,17 @@ public static class MinestomCommand extends Command { public MinestomCommand(HypixelCommand command) { super(command.getName()); - setDefaultExecutor((sender, context) -> { - sender.sendMessage("§cUsage: " + command.getParams().usage()); - }); + setDefaultExecutor((sender, _) -> sender.sendMessage("§cUsage: " + command.getParams().usage())); - setCondition((commandSender, string) -> { + setCondition((commandSender, _) -> { if (commandSender instanceof ConsoleSender) { return command.getParams().allowsConsole(); } HypixelPlayer player = (HypixelPlayer) commandSender; - HypixelDataHandler dataHandler = player.getDataHandler(); + Optional dataHandler = player.getOptionalDataHandler(); + return dataHandler.map(hypixelDataHandler -> hypixelDataHandler.get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(command.getParams().permission())).orElse(false); - return dataHandler.get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(command.getParams().permission()); }); command.registerUsage(this); @@ -88,19 +91,19 @@ public MinestomCommand(HypixelCommand command) { public MinestomCommand(HypixelCommand command, String... aliases) { super(command.getName(), aliases); - setDefaultExecutor((sender, context) -> { + setDefaultExecutor((sender, _) -> { sender.sendMessage("§cUsage: " + command.getParams().usage()); }); - setCondition((commandSender, string) -> { + setCondition((commandSender, _) -> { if (commandSender instanceof ConsoleSender) { return command.getParams().allowsConsole(); } HypixelPlayer player = (HypixelPlayer) commandSender; - HypixelDataHandler dataHandler = player.getDataHandler(); + Optional dataHandler = player.getOptionalDataHandler(); + return dataHandler.map(hypixelDataHandler -> hypixelDataHandler.get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(command.getParams().permission())).orElse(false); - return dataHandler.get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue().isEqualOrHigherThan(command.getParams().permission()); }); command.registerUsage(this); diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/DataLoadException.java b/type.generic/src/main/java/net/swofty/type/generic/data/DataLoadException.java new file mode 100644 index 0000000000..adb8c84557 --- /dev/null +++ b/type.generic/src/main/java/net/swofty/type/generic/data/DataLoadException.java @@ -0,0 +1,7 @@ +package net.swofty.type.generic.data; + +public class DataLoadException extends RuntimeException { + public DataLoadException(String message) { + super(message); + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointToggles.java b/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointToggles.java index 1d669f73b1..3a019186e4 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointToggles.java +++ b/type.generic/src/main/java/net/swofty/type/generic/data/datapoints/DatapointToggles.java @@ -122,6 +122,8 @@ public enum ToggleType { HAS_SPOKEN_TO_FISHERWOMAN_ENID(false), HAS_SPOKEN_TO_CAPTAIN_BAHA(false), HAS_SPOKEN_TO_RODDY(false), + HAS_SPOKEN_TO_SAM(false), + HAS_ACCEPTED_SAM_GARDEN(false), HAS_CAUGHT_FIRST_FISH(false), HAS_UNLOCKED_SHIP(false), HAS_VISITED_BACKWATER_BAYOU(false), diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/handlers/PrototypeLobbyDataHandler.java b/type.generic/src/main/java/net/swofty/type/generic/data/handlers/PrototypeLobbyDataHandler.java index ab771f9edb..58baae760b 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/data/handlers/PrototypeLobbyDataHandler.java +++ b/type.generic/src/main/java/net/swofty/type/generic/data/handlers/PrototypeLobbyDataHandler.java @@ -11,14 +11,14 @@ import org.tinylog.Logger; import tools.jackson.core.JacksonException; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Function; public class PrototypeLobbyDataHandler extends DataHandler implements GameDataHandler { - public static final Map prototypeLobbyCache = new HashMap<>(); + public static final Map prototypeLobbyCache = new ConcurrentHashMap<>(); public PrototypeLobbyDataHandler() { super(); } public PrototypeLobbyDataHandler(UUID uuid) { super(uuid); } diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/HypixelNPC.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/HypixelNPC.java index 6158eaa9a3..34f1982eee 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/HypixelNPC.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/HypixelNPC.java @@ -135,7 +135,13 @@ public static void updateForPlayer(HypixelPlayer player) { return; } - Pos npcPosition = entity.getPosition(); + if (entity.getInstance() != config.instance(player)) { + entity.remove(); + cache.remove(npc); + return; + } + + Pos npcPosition = config.position(player); if (!(entity instanceof NPCViewable npcViewable)) { Logger.error("Entity for NPC {} does not implement NPCViewable, skipping update", npc.getName(player)); return; @@ -252,7 +258,7 @@ public void stopWalking() { } public void sendNPCMessage(HypixelPlayer player, String message) { - sendNPCMessage(player, Component.text(message)); + sendNPCMessage(player, LegacyComponentSerializer.legacySection().deserialize(message)); } public void sendNPCMessage(HypixelPlayer player, Component message) { @@ -381,7 +387,7 @@ public DialogueSetBuilder lines(String[] lines) { Component[] components = new Component[lines.length]; for (int i = 0; i < lines.length; i++) { - components[i] = Component.text(lines[i]); + components[i] = LegacyComponentSerializer.legacySection().deserialize(lines[i]); } this.lines = components; return this; diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/NPCOption.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/NPCOption.java index 7689b445a3..b98b3aabe3 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/NPCOption.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/NPCOption.java @@ -24,11 +24,27 @@ public static void sendOption( boolean prefix, List