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 ec8113c04..2b26ac18e 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 @@ -112,6 +112,8 @@ public enum ItemType { APPLE(Material.APPLE, Rarity.COMMON), + ARACHNE_FRAGMENT(Material.PLAYER_HEAD, Rarity.RARE), + ARCHFIEND_DICE(Material.PLAYER_HEAD, Rarity.EPIC), ARCHITECTS_FIRST_DRAFT(Material.PAPER, Rarity.COMMON), @@ -204,6 +206,8 @@ public enum ItemType { BAT_TALISMAN(Material.PLAYER_HEAD, Rarity.RARE), + BEADY_EYES(Material.SPIDER_EYE, Rarity.UNCOMMON), + BEASTMASTER_CREST_COMMON(Material.PLAYER_HEAD, Rarity.COMMON), BEASTMASTER_CREST_EPIC(Material.PLAYER_HEAD, Rarity.EPIC), @@ -376,6 +380,8 @@ public enum ItemType { BOOKSHELF(Material.BOOKSHELF, Rarity.COMMON), + BOOKWORMS_FAVORITE_BOOK(Material.BOOK, Rarity.RARE), + BOOSTER_COOKIE(Material.COOKIE, Rarity.COMMON), BOW(Material.BOW, Rarity.COMMON), @@ -436,6 +442,8 @@ public enum ItemType { BURNING_KUUDRA_CORE(Material.PLAYER_HEAD, Rarity.RARE), + BURROWING_SPORES(Material.BROWN_MUSHROOM, Rarity.RARE), + BURSTSTOPPER_ARTIFACT(Material.PLAYER_HEAD, Rarity.EPIC), BURSTSTOPPER_TALISMAN(Material.PLAYER_HEAD, Rarity.RARE), @@ -516,6 +524,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_CRYSTAL(Material.PLAYER_HEAD, Rarity.COMMON), @@ -624,8 +634,12 @@ public enum ItemType { CLAY_MINION(Material.PLAYER_HEAD, Rarity.COMMON), + 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), @@ -674,6 +688,8 @@ public enum ItemType { CONTROL_SWITCH(Material.PLAYER_HEAD, Rarity.COMMON), + COPPER_DYE(Material.ORANGE_DYE, Rarity.SPECIAL), + CORLEONITE(Material.PLAYER_HEAD, Rarity.COMMON), CORNFLOWER(Material.CORNFLOWER, Rarity.COMMON), @@ -724,8 +740,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), CRYSTALLIZED_HEART(Material.PLAYER_HEAD, Rarity.COMMON), @@ -860,6 +880,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.COMMON), DEFENSE_ENRICHMENT(Material.PLAYER_HEAD, Rarity.COMMON), @@ -934,6 +956,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.RARE), DWARVEN_SACK(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1272,12 +1296,16 @@ public enum ItemType { ETERNAL_FLAME_RING(Material.PLAYER_HEAD, Rarity.COMMON), + 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.COMMON), + 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), @@ -1298,6 +1326,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), FARM_SUIT_BOOTS(Material.LEATHER_BOOTS, Rarity.COMMON), @@ -1318,6 +1348,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), @@ -1338,6 +1370,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), @@ -1356,6 +1390,8 @@ public enum ItemType { FIREWORK_ROCKET(Material.FIREWORK_ROCKET, Rarity.COMMON), + FIRE_IN_A_BOTTLE(Material.EXPERIENCE_BOTTLE, Rarity.RARE), + FIRE_RESISTANCE_POTION(Material.POTION, Rarity.COMMON), FIRE_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1426,6 +1462,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), @@ -1448,6 +1486,8 @@ public enum ItemType { FROZEN_CHICKEN(Material.PLAYER_HEAD, Rarity.RARE), + FRUIT_BOWL(Material.BOWL, Rarity.RARE), + FTX_3070(Material.PLAYER_HEAD, Rarity.COMMON), FUMING_POTATO_BOOK(Material.BOOK, Rarity.EPIC), @@ -1594,6 +1634,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), @@ -1620,6 +1664,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), @@ -1640,6 +1686,8 @@ public enum ItemType { HARD_STONE_MINION(Material.PLAYER_HEAD, Rarity.COMMON), + HARVEST_HARBINGER_POTION(Material.POTION, Rarity.RARE), + HASTE_ARTIFACT(Material.PLAYER_HEAD, Rarity.EPIC), HASTE_POTION_1(Material.POTION, Rarity.COMMON), @@ -1702,6 +1750,8 @@ public enum ItemType { HUNTER_TALISMAN(Material.PLAYER_HEAD, Rarity.UNCOMMON), + HYPERCHARGE_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + HYPERION(Material.IRON_SWORD, Rarity.LEGENDARY), HYPER_CATALYST_UPGRADER(Material.PLAYER_HEAD, Rarity.COMMON), @@ -1732,6 +1782,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), @@ -1770,6 +1822,8 @@ public enum ItemType { JACK_O_LANTERN(Material.JACK_O_LANTERN, Rarity.COMMON), + JELLY(Material.SLIME_BALL, Rarity.COMMON), + JERRY_TALISMAN_BLUE(Material.PLAYER_HEAD, Rarity.RARE), JERRY_TALISMAN_GOLDEN(Material.PLAYER_HEAD, Rarity.LEGENDARY), @@ -2070,6 +2124,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), @@ -2086,6 +2142,8 @@ public enum ItemType { MATRIARCHS_PERFUME(Material.RABBIT_STEW, Rarity.COMMON), + MECHAMIND_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + MEDIUM_AGRONOMY_SACK(Material.PLAYER_HEAD, Rarity.RARE), MEDIUM_BACKPACK(Material.PLAYER_HEAD, Rarity.COMMON), @@ -2216,6 +2274,8 @@ public enum ItemType { MYCELIUM(Material.MYCELIUM, Rarity.COMMON), + MYSTERIOUS_CROP(Material.DEAD_BUSH, Rarity.COMMON), + NECRONS_HANDLE(Material.STICK, Rarity.COMMON), NETHERRACK(Material.NETHERRACK, Rarity.COMMON), @@ -2338,8 +2398,12 @@ public enum ItemType { ORANGE_WOOL(Material.ORANGE_WOOL, Rarity.COMMON), + 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), @@ -2430,6 +2494,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), PICKONIMBUS_2000(Material.DIAMOND_PICKAXE, Rarity.COMMON), @@ -2626,6 +2692,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_FOOT(Material.RABBIT_FOOT, Rarity.COMMON), @@ -2638,6 +2706,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), @@ -3026,6 +3096,12 @@ public enum ItemType { SOUL_STRING(Material.STRING, Rarity.COMMON), + SOWDUST(Material.GREEN_DYE, Rarity.COMMON), + + SOWLEDGE_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + + SPACE_HELMET(Material.PLAYER_HEAD, Rarity.SPECIAL), + SPEED_ARTIFACT(Material.PLAYER_HEAD, Rarity.COMMON), SPEED_ENRICHMENT(Material.PLAYER_HEAD, Rarity.COMMON), @@ -3096,8 +3172,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.COMMON), SQUIRE_CHESTPLATE(Material.IRON_CHESTPLATE, Rarity.COMMON), @@ -3228,8 +3310,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.COMMON), @@ -3242,6 +3330,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.COMMON), TALL_GRASS(Material.TALL_GRASS, Rarity.COMMON), @@ -3250,6 +3340,8 @@ public enum ItemType { TARANTULA_WEB(Material.STRING, Rarity.UNCOMMON), + TASTY_CHEESE(Material.HONEYCOMB, Rarity.COMMON), + TENDER_WOOD(Material.PLAYER_HEAD, Rarity.COMMON), TEPID_GREEN_TEA(Material.PLAYER_HEAD, Rarity.COMMON), @@ -3318,6 +3410,8 @@ public enum ItemType { VACCINE_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), + VERMIN_VAPORIZER_GARDEN_CHIP(Material.PLAYER_HEAD, Rarity.RARE), + VILLAGE_AFFINITY_TALISMAN(Material.PLAYER_HEAD, Rarity.COMMON), VINES(Material.VINE, Rarity.COMMON), @@ -3416,6 +3510,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), WISHING_COMPASS(Material.PLAYER_HEAD, Rarity.COMMON), @@ -3448,6 +3544,8 @@ public enum ItemType { WORM_MEMBRANE(Material.ROTTEN_FLESH, Rarity.COMMON), + WRIGGLING_LARVA(Material.STRING, Rarity.RARE), + YELLOW_BANNER(Material.YELLOW_BANNER, Rarity.COMMON), YELLOW_BED(Material.YELLOW_BED, 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 971b4fac1..de5be98fa 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 b72b4a97e..8888e30bb 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, PUNISHMENT, 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 000000000..d633315a2 --- /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.ProtocolObject; +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 ProtocolObject< + 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/commons/src/main/java/net/swofty/commons/service/FromServiceChannels.java b/commons/src/main/java/net/swofty/commons/service/FromServiceChannels.java index 25dbf8cbf..08c5a2a3c 100644 --- a/commons/src/main/java/net/swofty/commons/service/FromServiceChannels.java +++ b/commons/src/main/java/net/swofty/commons/service/FromServiceChannels.java @@ -16,6 +16,7 @@ public enum FromServiceChannels { INSTANTIATE_GAME("instantiate-game"), DARK_AUCTION_EVENT("dark-auction-event"), TRIGGER_DARK_AUCTION("trigger-dark-auction"), + JACOBS_CONTEST_EVENT("jacobs-contest-event"), PROPAGATE_FRIEND_EVENT("propagate_friend_event"), ; @@ -33,4 +34,4 @@ public static FromServiceChannels getChannelName(String channel) { } throw new IllegalArgumentException("Unknown channelName: " + channel); } -} \ No newline at end of file +} diff --git a/configuration/i18n/en_US/scoreboard.properties b/configuration/i18n/en_US/scoreboard.properties index 87d2abf5b..833048637 100644 --- a/configuration/i18n/en_US/scoreboard.properties +++ b/configuration/i18n/en_US/scoreboard.properties @@ -1,5 +1,3 @@ -# Scoreboard strings for all game types - # Common scoreboard.common.date_format = MM/dd/yy scoreboard.common.footer = www.hypixel.net @@ -7,8 +5,8 @@ scoreboard.common.footer = www.hypixel.net # SkyBlock Scoreboard scoreboard.skyblock.title_base = SKYBLOCK scoreboard.skyblock.coop_suffix = CO-OP -scoreboard.skyblock.purse_label = Purse: -scoreboard.skyblock.bits_label = Bits: +scoreboard.skyblock.purse=Purse: +scoreboard.skyblock.bits=Bits: scoreboard.skyblock.region_unknown = Unknown scoreboard.skyblock.objective_label = Objective scoreboard.skyblock.dark_auction.time_left_label = Time Left: diff --git a/configuration/skyblock/furniture/composter.json b/configuration/skyblock/furniture/composter.json new file mode 100644 index 000000000..07ac7fb7e --- /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 000000000..6f9458fa5 --- /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 000000000..99a0f0848 --- /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 000000000..bf4263d46 --- /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 000000000..de5a4f6b5 --- /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 000000000..26fddf8c2 --- /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 000000000..9a1498fcf --- /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 000000000..4893a2314 --- /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 000000000..ab11ca3d7 --- /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 000000000..c1161cd05 --- /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 000000000..9aa181e42 --- /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 000000000..9765011aa --- /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 000000000..2860e950b --- /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 000000000..1a7eb5195 --- /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 000000000..1d256a502 --- /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 000000000..e9343ef35 --- /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 000000000..82afb1bdd --- /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 000000000..ae4bc3237 --- /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 000000000..2636b05ff --- /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 000000000..3e38fe4e4 --- /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 000000000..c8518ca40 --- /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 000000000..b464e9eb5 --- /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/garden_static_code_snippets/GUIAnita.java b/garden_static_code_snippets/GUIAnita.java new file mode 100644 index 000000000..65e72b505 --- /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 000000000..025caf6e1 --- /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 000000000..a5cb1e587 --- /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 000000000..388fe3fb9 --- /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 000000000..18667f9f9 --- /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 000000000..dae0a48ca --- /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 000000000..70f26562f --- /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 000000000..a3aa90f68 --- /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 000000000..9f600506e --- /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 000000000..6e527d41c --- /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 000000000..c823478ea --- /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 000000000..d5297d02e --- /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 000000000..a16e13af0 --- /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 000000000..88b07c09d --- /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 000000000..bb5103b88 --- /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 000000000..294207b25 --- /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 000000000..ea8510468 --- /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 000000000..edab26e15 --- /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 000000000..f3a01f0ff --- /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 000000000..53b4d3df8 --- /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 000000000..939ead8ff --- /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 000000000..f3fa1e9cd --- /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 ef7350c31..13f0f7d5a 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/server/proxy/velocity.jar b/server/proxy/velocity.jar index 09a0a0c1d..5043b1eee 100644 Binary files a/server/proxy/velocity.jar and b/server/proxy/velocity.jar differ diff --git a/service.darkauction/build.gradle.kts b/service.darkauction/build.gradle.kts index 44275925b..0a7a7605c 100644 --- a/service.darkauction/build.gradle.kts +++ b/service.darkauction/build.gradle.kts @@ -3,7 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { java application - id("io.github.goooler.shadow") version "8.1.8" + id("com.gradleup.shadow") version "9.3.1" } group = "net.swofty" diff --git a/service.jacobscontest/build.gradle.kts b/service.jacobscontest/build.gradle.kts new file mode 100644 index 000000000..56f314df7 --- /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 000000000..b4274e0cc --- /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 000000000..242f9f064 --- /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 000000000..12729b901 --- /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.service.generic.SkyBlockService; +import net.swofty.service.generic.redis.ServiceEndpoint; + +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", ServiceEndpoint.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 000000000..797c2a93d --- /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.impl.ServiceProxyRequest; +import net.swofty.commons.protocol.ProtocolObject; +import net.swofty.commons.protocol.objects.jacobscontest.GetJacobContestScheduleProtocol; +import net.swofty.service.generic.redis.ServiceEndpoint; +import net.swofty.service.jacobscontest.JacobsContestScheduler; + +import java.util.List; + +public class EndpointGetJacobContestSchedule implements ServiceEndpoint< + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage, + GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse> { + + @Override + public ProtocolObject< + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage, + GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse> associatedProtocolObject() { + return new GetJacobContestScheduleProtocol(); + } + + @Override + public GetJacobContestScheduleProtocol.GetJacobContestScheduleResponse onMessage( + ServiceProxyRequest message, + GetJacobContestScheduleProtocol.GetJacobContestScheduleMessage body + ) { + 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.orchestrator/build.gradle.kts b/service.orchestrator/build.gradle.kts index f06fd7367..6fb4006e0 100644 --- a/service.orchestrator/build.gradle.kts +++ b/service.orchestrator/build.gradle.kts @@ -3,7 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { java application - id("io.github.goooler.shadow") version "8.1.8" + id("com.gradleup.shadow") version "9.3.1" } group = "net.swofty" @@ -40,4 +40,4 @@ tasks { archiveClassifier.set("") archiveVersion.set("") } -} \ No newline at end of file +} diff --git a/service.punishment/build.gradle.kts b/service.punishment/build.gradle.kts index d4cfcdfae..8c2f38c88 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 aa5316412..87f2ade01 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,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") @@ -49,6 +50,7 @@ include(":service.datamutex") include(":service.party") include(":service.orchestrator") include(":service.darkauction") +include(":service.jacobscontest") include(":service.friend") include(":service.punishment") include(":anticheat") diff --git a/type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/BedWarsGameScoreboard.java b/type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/BedWarsGameScoreboard.java index 902e83e69..eba3e7ef1 100644 --- a/type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/BedWarsGameScoreboard.java +++ b/type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/BedWarsGameScoreboard.java @@ -1,12 +1,14 @@ package net.swofty.type.bedwarsgame; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; import net.minestom.server.timer.TaskSchedule; +import net.swofty.commons.bedwars.map.BedWarsMapsConfig.TeamKey; import net.swofty.type.bedwarsgame.game.Game; import net.swofty.type.bedwarsgame.game.GameStatus; -import net.swofty.commons.bedwars.map.BedWarsMapsConfig.TeamKey; import net.swofty.type.generic.HypixelConst; import net.swofty.type.generic.data.HypixelDataHandler; import net.swofty.type.generic.data.handlers.BedWarsDataHandler; @@ -47,18 +49,18 @@ public static void start(Game game) { continue; } - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); if (game.getGameStatus() == GameStatus.WAITING) { - lines.add(I18n.string("scoreboard.bedwars_game.map_label") + game.getMapEntry().getName()); - lines.add(I18n.string("scoreboard.bedwars_game.players_label") + game.getPlayers().size() + "/" + game.getMapEntry().getConfiguration().getTeams().size()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_game.starting_in_label") + game.getCountdown().getRemainingSeconds() + I18n.string("scoreboard.bedwars_game.starting_in_suffix")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_game.mode_label") + game.getBedwarsGameType().getDisplayName()); - lines.add(I18n.string("scoreboard.bedwars_game.version_label")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.map_label") + game.getMapEntry().getName())); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.players_label") + game.getPlayers().size() + "/" + game.getMapEntry().getConfiguration().getTeams().size())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.starting_in_label") + game.getCountdown().getRemainingSeconds() + I18n.string("scoreboard.bedwars_game.starting_in_suffix"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.mode_label") + game.getBedwarsGameType().getDisplayName())); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.version_label"))); } else { String eventName = game.getEventManager().getNextEvent() != null ? game.getEventManager().getNextEvent().getDisplayName() @@ -67,8 +69,8 @@ public static void start(Game game) { long minutesPart = seconds / 60; long secondsPart = seconds % 60; String timeLeft = String.format("%d:%02d", minutesPart, secondsPart); - lines.add(I18n.string("scoreboard.bedwars_game.event_in_label", Map.of("event_name", eventName, "time_left", timeLeft))); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_game.event_in_label", Map.of("event_name", eventName, "time_left", timeLeft)))); + lines.add(HypixelScoreboard.legacy("§7 ")); for (Map.Entry entry : game.getMapEntry().getConfiguration().getTeams().entrySet()) { TeamKey teamKey = entry.getKey(); String teamName = teamKey.getName(); @@ -77,11 +79,11 @@ public static void start(Game game) { String bedStatus = game.getTeamManager().isBedAlive(teamKey) ? I18n.string("scoreboard.bedwars_game.bed_alive") : I18n.string("scoreboard.bedwars_game.bed_dead"); - lines.add(String.format("%s%s §f%s %s", teamKey.chatColor(), teamInitial, teamName, bedStatus)); + lines.add(HypixelScoreboard.legacy(String.format("%s%s §f%s %s", teamKey.chatColor(), teamInitial, teamName, bedStatus))); } } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(prototypeName)); @@ -98,21 +100,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.bedwars_game.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - - if (counter > 0 && counter <= 8) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter) + - endColor; - } else if ((counter >= 9 && counter <= 19) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText + endColor; - } else { - return colors[2] + baseText + endColor; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.bedwars_game.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.GOLD, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 8, + 9, + 19, + 25, + 29, + Component.text("BED WARS", NamedTextColor.GREEN, net.kyori.adventure.text.format.TextDecoration.BOLD) + ); } } diff --git a/type.bedwarslobby/src/main/java/net/swofty/type/bedwarslobby/BedWarsLobbyScoreboard.java b/type.bedwarslobby/src/main/java/net/swofty/type/bedwarslobby/BedWarsLobbyScoreboard.java index d19d50e37..712fe6591 100644 --- a/type.bedwarslobby/src/main/java/net/swofty/type/bedwarslobby/BedWarsLobbyScoreboard.java +++ b/type.bedwarslobby/src/main/java/net/swofty/type/bedwarslobby/BedWarsLobbyScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.bedwarslobby; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -62,21 +64,21 @@ public static void start() { long tokens = bwDataHandler.get(BedWarsDataHandler.Data.TOKENS, DatapointLeaderboardLong.class).getValue(); long tickets = bwDataHandler.get(BedWarsDataHandler.Data.SLUMBER_TICKETS, DatapointLeaderboardLong.class).getValue(); - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_lobby.level_label") + BedwarsLevelColor.constructLevelString(BedwarsLevelUtil.calculateLevel(experience))); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_lobby.progress_label") + suffix(progress) + I18n.string("scoreboard.bedwars_lobby.progress_separator") + suffix(maxExperience)); - lines.add(progressBar.toString()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_lobby.tokens_label") + tokens); - lines.add(I18n.string("scoreboard.bedwars_lobby.tickets_label") + tickets + I18n.string("scoreboard.bedwars_lobby.tickets_max")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.bedwars_lobby.total_kills_label")); - lines.add(I18n.string("scoreboard.bedwars_lobby.total_wins_label")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.level_label") + BedwarsLevelColor.constructLevelString(BedwarsLevelUtil.calculateLevel(experience)))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.progress_label") + suffix(progress) + I18n.string("scoreboard.bedwars_lobby.progress_separator") + suffix(maxExperience))); + lines.add(HypixelScoreboard.legacy(progressBar.toString())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.tokens_label") + tokens)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.tickets_label") + tickets + I18n.string("scoreboard.bedwars_lobby.tickets_max"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.total_kills_label"))); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.bedwars_lobby.total_wins_label"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(prototypeName)); @@ -93,21 +95,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.bedwars_lobby.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - - if (counter > 0 && counter <= 8) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter) + - endColor; - } else if ((counter >= 9 && counter <= 19) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText + endColor; - } else { - return colors[2] + baseText + endColor; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.bedwars_lobby.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.GOLD, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 8, + 9, + 19, + 25, + 29, + Component.text("BED WARS", NamedTextColor.GREEN, net.kyori.adventure.text.format.TextDecoration.BOLD) + ); } } 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 5b4393a61..3b00a7e60 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 @@ -5,9 +5,10 @@ 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.progression.GardenSpokenNpcSource; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCRhys extends HypixelNPC { +public class NPCRhys extends HypixelNPC implements GardenSpokenNpcSource { public NPCRhys() { super(new HumanConfiguration() { @@ -43,4 +44,9 @@ public void onClick(NPCInteractEvent event) { SkyBlockPlayer player = (SkyBlockPlayer) event.player(); player.notImplemented(); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "RHYS"; + } +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/AbstractEmissary.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/AbstractEmissary.java index 2c7dfde10..1c8a7edf4 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/AbstractEmissary.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/AbstractEmissary.java @@ -8,7 +8,8 @@ import net.swofty.type.generic.user.HypixelPlayer; import org.jspecify.annotations.NonNull; -public abstract class AbstractEmissary extends HypixelNPC { +public abstract class AbstractEmissary extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { + private final String gardenSpokenNpcId; public AbstractEmissary(String name, String texture, String signature, Pos pos) { super(new HumanConfiguration() { @@ -42,6 +43,10 @@ public boolean looking(HypixelPlayer player) { return "§6" + name; } }); + this.gardenSpokenNpcId = "EMISSARY_" + name + .trim() + .replace(' ', '_') + .toUpperCase(java.util.Locale.ROOT); } @Override @@ -49,4 +54,9 @@ public void onClick(NPCInteractEvent event) { HypixelPlayer player = event.player(); new GUICommisions().open(player); } + + @Override + public String gardenSpokenNpcId() { + return gardenSpokenNpcId; + } } 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 4f69cfcfc..5d7b87794 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,12 +3,11 @@ 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 class NPCBankerBroadjaw extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCBankerBroadjaw() { super(new HumanConfiguration() { @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIBanker().open(event.player()); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "BANKER_BROADJAW"; + } +} 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 c93ec2fdf..ce769c843 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,11 +4,10 @@ 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 { +public class NPCFragilis extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCFragilis() { super(new HumanConfiguration() { @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIHandyBlockGuide().open(event.player()); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "FRAGILIS"; + } +} diff --git a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCLumina.java b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCLumina.java index dd88036e9..3fbd6f7c3 100644 --- a/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCLumina.java +++ b/type.dwarvenmines/src/main/java/net/swofty/type/dwarvenmines/npcs/NPCLumina.java @@ -9,7 +9,7 @@ import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCLumina extends HypixelNPC { +public class NPCLumina extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCLumina() { super(new HumanConfiguration() { @Override @@ -48,4 +48,9 @@ public void onClick(NPCInteractEvent event) { player.openView(new GUIShopLumina()); } + + @Override + public String gardenSpokenNpcId() { + return "LUMINA"; + } } 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 85134d3e4..03a561be4 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 @@ -5,9 +5,10 @@ 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.progression.GardenSpokenNpcSource; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCPuzzler extends HypixelNPC { +public class NPCPuzzler extends HypixelNPC implements GardenSpokenNpcSource { public NPCPuzzler() { super(new HumanConfiguration() { @@ -44,4 +45,8 @@ public void onClick(NPCInteractEvent event) { player.notImplemented(); } -} \ No newline at end of file + @Override + public String gardenSpokenNpcId() { + return "PUZZLER"; + } +} 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 56b8ff811..098112f95 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,11 +4,10 @@ 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 { +public class NPCRhys extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCRhys() { super(new HumanConfiguration() { @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { new GUIGuide().open(event.player()); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "RHYS"; + } +} diff --git a/type.garden/build.gradle.kts b/type.garden/build.gradle.kts new file mode 100644 index 000000000..f754cc25d --- /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 000000000..87f5e54d8 --- /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 000000000..8c3fd734b --- /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 000000000..4acd9884d --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/TypeGardenLoader.java @@ -0,0 +1,132 @@ +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.proxyapi.redis.ProxyToClient; +import net.swofty.proxyapi.redis.ServiceToClient; +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 getServiceRedisListeners() { + return List.of(); + } + + @Override + public List getProxyRedisListeners() { + 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 000000000..656de9c70 --- /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 000000000..e94b2c31b --- /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( + aliases = "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 000000000..4ae903a66 --- /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 000000000..89a30e33d --- /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 000000000..6b25ffdd4 --- /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 000000000..968ba8786 --- /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 000000000..882a3be55 --- /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 000000000..55aa2a5bd --- /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.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +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 { + @HypixelEvent(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 000000000..8c066cc3c --- /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.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class ActionGardenSave implements HypixelEventClass { + @SneakyThrows + @HypixelEvent(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 000000000..3a943c98d --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerJoin.java @@ -0,0 +1,20 @@ +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.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +public class ActionPlayerJoin implements HypixelEventClass { + @SneakyThrows + @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = false) + 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 000000000..7d04c7b07 --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/events/traditional/ActionPlayerTeleport.java @@ -0,0 +1,35 @@ +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.HypixelEvent; +import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.UUID; + +public class ActionPlayerTeleport implements HypixelEventClass { + @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = false) + public void run(PlayerSpawnEvent event) { + SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); + if (!event.isFirstSpawn()) { + return; + } + if (!player.hasAuthenticated) { + 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 000000000..a49c2b5d3 --- /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 000000000..d147721f7 --- /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 000000000..5ada2b8fa --- /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 000000000..514919e45 --- /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 000000000..b35842ffc --- /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 000000000..a86f7fd2c --- /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 000000000..84b4923fe --- /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 000000000..591e5fd25 --- /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 000000000..6cfd4861d --- /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 000000000..eda1782dc --- /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 000000000..391d4adfa --- /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 000000000..1468069b0 --- /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 000000000..a1b21ad8c --- /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 000000000..d0d09ad09 --- /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 000000000..1c97c0a9b --- /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 000000000..b8c11417e --- /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 000000000..accc93004 --- /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 000000000..fa196df50 --- /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 000000000..dc8daba4a --- /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 000000000..788bb9b29 --- /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 000000000..e9eba16f6 --- /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 000000000..037053440 --- /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 000000000..722d26049 --- /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 000000000..01b5106b5 --- /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 000000000..672e5da32 --- /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 000000000..ca4a0322f --- /dev/null +++ b/type.garden/src/main/java/net/swofty/type/garden/npc/AbstractGardenNpc.java @@ -0,0 +1,95 @@ +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.garden.progression.GardenSpokenNpcSource; +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 implements GardenSpokenNpcSource { + 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)); + } + + @Override + public String gardenSpokenNpcId() { + return 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 000000000..1f401d25b --- /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 000000000..d4e188320 --- /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 000000000..02fdd94e1 --- /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 000000000..3bc8adc19 --- /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 000000000..5e0c912f8 --- /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 000000000..ca48462b8 --- /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 000000000..47816db95 --- /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 000000000..3b48d6c42 --- /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 000000000..6771f1f53 --- /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 000000000..e44d8c46e --- /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 000000000..9ace2449d --- /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 000000000..887e6adc5 --- /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 000000000..045501dfd --- /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 000000000..4dfac09dd --- /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 000000000..ed17ea90c --- /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 000000000..7115843cc --- /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 000000000..a00bc2f59 --- /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 000000000..2fd4217af --- /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 000000000..73fce0741 --- /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 1307412a3..3b07d85e3 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 @@ -46,4 +46,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 a0193e526..c01f3de63 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 { public static final String COMMAND_SUFFIX = "Command"; @@ -42,8 +43,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."); @@ -57,19 +62,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); @@ -78,19 +81,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/command/commands/LobbyCommand.java b/type.generic/src/main/java/net/swofty/type/generic/command/commands/LobbyCommand.java deleted file mode 100644 index 86c9c3cb7..000000000 --- a/type.generic/src/main/java/net/swofty/type/generic/command/commands/LobbyCommand.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.swofty.type.generic.command.commands; - -import net.minestom.server.command.builder.arguments.ArgumentEnum; -import net.minestom.server.command.builder.arguments.ArgumentType; -import net.minestom.server.entity.GameMode; -import net.swofty.commons.ServerType; -import net.swofty.type.generic.command.CommandParameters; -import net.swofty.type.generic.command.HypixelCommand; -import net.swofty.type.generic.user.HypixelPlayer; -import net.swofty.type.generic.user.categories.Rank; - -@CommandParameters(aliases = "l", - description = "Takes the player to the lobby", - usage = "/lobby", - permission = Rank.DEFAULT, - allowsConsole = false) -public class LobbyCommand extends HypixelCommand { - - @Override - public void registerUsage(MinestomCommand command) { - command.addSyntax((sender, context) -> { - if (!permissionCheck(sender)) return; - - HypixelPlayer player = (HypixelPlayer) sender; - player.sendTo(ServerType.PROTOTYPE_LOBBY); - }); - } - -} diff --git a/type.generic/src/main/java/net/swofty/type/generic/data/DataHandler.java b/type.generic/src/main/java/net/swofty/type/generic/data/DataHandler.java index c4956adc3..0fe5764a2 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/data/DataHandler.java +++ b/type.generic/src/main/java/net/swofty/type/generic/data/DataHandler.java @@ -9,18 +9,19 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +@Getter public abstract class DataHandler { - public static Map userCache = new HashMap<>(); + public static Map userCache = new ConcurrentHashMap<>(); - @Getter protected UUID uuid; + protected UUID uuid; protected final Map> datapoints = new HashMap<>(); protected DataHandler() {} protected DataHandler(UUID uuid) { this.uuid = uuid; } public Datapoint getDatapoint(String key) { return this.datapoints.get(key); } - public Map> getDatapoints() { return this.datapoints; } public static @NonNull DataHandler getUser(UUID uuid) { if (!userCache.containsKey(uuid)) throw new RuntimeException("User " + uuid + " does not exist!"); 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 000000000..adb8c8455 --- /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/HypixelDataHandler.java b/type.generic/src/main/java/net/swofty/type/generic/data/HypixelDataHandler.java index 48fdbc5ad..75fbcaa91 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/data/HypixelDataHandler.java +++ b/type.generic/src/main/java/net/swofty/type/generic/data/HypixelDataHandler.java @@ -19,6 +19,7 @@ import org.bson.Document; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; import org.tinylog.Logger; import tools.jackson.core.JacksonException; @@ -31,13 +32,17 @@ public class HypixelDataHandler extends DataHandler { protected HypixelDataHandler() { super(); } public HypixelDataHandler(UUID uuid) { super(uuid); } - public static HypixelDataHandler getUser(UUID uuid) { - if (!userCache.containsKey(uuid)) throw new RuntimeException("User " + uuid + " does not exist!"); + public static @NonNull HypixelDataHandler getUser(UUID uuid) { + if (!userCache.containsKey(uuid)) throw new DataLoadException("User " + uuid + " does not exist!"); return (HypixelDataHandler) userCache.get(uuid); } public static @Nullable HypixelDataHandler getUser(Player player) { - try { return getUser(player.getUuid()); } catch (Exception e) { return null; } + try { + return getUser(player.getUuid()); + } catch (Exception e) { + return null; + } } @Override 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 9d6808e5a..58baae760 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); } @@ -125,7 +125,6 @@ public Datapoint get(Data datapoint) { return dp != null ? dp : datapoint.defaultDatapoint; } - @SuppressWarnings("unchecked") public > R get(Data datapoint, Class type) { Datapoint dp = this.datapoints.get(datapoint.key); return (R) (dp != null ? type.cast(dp) : type.cast(datapoint.defaultDatapoint)); 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 fc116d4fb..dba486694 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 @@ -146,6 +146,12 @@ public static void updateForPlayer(HypixelPlayer player) { return; } + 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()); diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/configuration/NPCConfiguration.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/configuration/NPCConfiguration.java index 88ab0f2aa..470b69069 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/configuration/NPCConfiguration.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/configuration/NPCConfiguration.java @@ -30,6 +30,10 @@ default Instance instance() { return HypixelConst.getInstanceContainer(); } + default Instance instance(HypixelPlayer player) { + return instance(); + } + default EntityPose pose(HypixelPlayer player) { return EntityPose.STANDING; } diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCAnimalEntityImpl.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCAnimalEntityImpl.java index a6b3be06b..76c6c5f24 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCAnimalEntityImpl.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCAnimalEntityImpl.java @@ -40,13 +40,13 @@ public NPCAnimalEntityImpl(@NotNull HypixelPlayer viewer, @NotNull Pos pos, @Not .pos(pos.add(0, getEyeHeight() + config.hologramYOffset() + (overflowing ? -0.2f : 0f), 0)) .text(Arrays.copyOfRange(holograms, 0, holograms.length - (overflowing ? 0 : 1))) .player(viewer) - .instance(config.instance()) + .instance(config.instance(viewer)) .build(); this.holo = holo; PlayerHolograms.addExternalPlayerHologram(holo); - setInstance(config.instance(), pos); + setInstance(config.instance(viewer), pos); addViewer(viewer); setPose(config.pose(viewer)); } @@ -68,13 +68,15 @@ public void clearCache(HypixelPlayer player) { @Override public void updateNPC() { Pos npcPosition = config.position(viewer); - if (!getPosition().asVec().equals(npcPosition.asVec())) { + if (!getPosition().samePoint(npcPosition)) { + Pos nextPosition = stepTowards(getPosition(), npcPosition, 0.85D); + teleport(nextPosition); String[] holograms = config.holograms(viewer); boolean overflowing = holograms[holograms.length - 1].length() > 16; float yOffset = overflowing ? -0.2f : 0.0f; yOffset += config.hologramYOffset(); - PlayerHolograms.relocateExternalPlayerHologram(holo, npcPosition.add(0, getEyeHeight() + yOffset, 0)); + PlayerHolograms.relocateExternalPlayerHologram(holo, nextPosition.add(0, getEyeHeight() + yOffset, 0)); } if (!getPose().equals(config.pose(viewer))) { @@ -89,4 +91,23 @@ public void updateNPC() { this.holograms = finalHolograms; } } + + private static Pos stepTowards(Pos current, Pos target, double maxDistance) { + double dx = target.x() - current.x(); + double dy = target.y() - current.y(); + double dz = target.z() - current.z(); + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (distance <= maxDistance || distance == 0D) { + return target; + } + + double multiplier = maxDistance / distance; + return new Pos( + current.x() + dx * multiplier, + current.y() + dy * multiplier, + current.z() + dz * multiplier, + target.yaw(), + target.pitch() + ); + } } diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCEntityImpl.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCEntityImpl.java index 5e27885ce..fee308247 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCEntityImpl.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCEntityImpl.java @@ -61,7 +61,7 @@ public NPCEntityImpl(@NotNull HypixelPlayer viewer, @NotNull Pos pos, @NotNull S .pos(pos.add(0, getEyeHeight() + 0.1f, 0)) .text(holograms) .player(viewer) - .instance(config.instance()) + .instance(config.instance(viewer)) .build(); this.holo = holo; @@ -69,7 +69,7 @@ public NPCEntityImpl(@NotNull HypixelPlayer viewer, @NotNull Pos pos, @NotNull S PlayerHolograms.addExternalPlayerHologram(holo); } - setInstance(config.instance(), pos); + setInstance(config.instance(viewer), pos); addViewer(viewer); setCustomNameVisible(false); @@ -124,8 +124,12 @@ public void tick(long time) { @Override public void updateNPC() { Pos npcPosition = config.position(viewer); - if (!getPosition().asVec().equals(npcPosition.asVec()) && config.shouldDisplayHolograms(viewer)) { - PlayerHolograms.relocateExternalPlayerHologram(holo, npcPosition.add(0, getEyeHeight() + 0.1f, 0)); + if (!getPosition().samePoint(npcPosition)) { + Pos nextPosition = stepTowards(getPosition(), npcPosition, 0.85D); + teleport(nextPosition); + if (config.shouldDisplayHolograms(viewer)) { + PlayerHolograms.relocateExternalPlayerHologram(holo, nextPosition.add(0, getEyeHeight() + 0.1f, 0)); + } } if (!getPose().equals(config.pose(viewer))) { @@ -164,4 +168,23 @@ public void updateNPC() { }); } } -} \ No newline at end of file + + private static Pos stepTowards(Pos current, Pos target, double maxDistance) { + double dx = target.x() - current.x(); + double dy = target.y() - current.y(); + double dz = target.z() - current.z(); + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (distance <= maxDistance || distance == 0D) { + return target; + } + + double multiplier = maxDistance / distance; + return new Pos( + current.x() + dx * multiplier, + current.y() + dy * multiplier, + current.z() + dz * multiplier, + target.yaw(), + target.pitch() + ); + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCVillagerEntityImpl.java b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCVillagerEntityImpl.java index eb4c5d021..69330935e 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCVillagerEntityImpl.java +++ b/type.generic/src/main/java/net/swofty/type/generic/entity/npc/impl/NPCVillagerEntityImpl.java @@ -49,12 +49,12 @@ public NPCVillagerEntityImpl(@NotNull HypixelPlayer viewer, Pos pos, @NotNull St .pos(pos.add(0, getEyeHeight() + 0.5f + (overflowing ? -0.2f : 0f), 0)) .text(Arrays.copyOfRange(holograms, 0, holograms.length - (overflowing ? 0 : 1))) .player(viewer) - .instance(config.instance()) + .instance(config.instance(viewer)) .build(); this.holo = holo; PlayerHolograms.addExternalPlayerHologram(holo); - setInstance(config.instance(), pos); + setInstance(config.instance(viewer), pos); addViewer(viewer); setPose(config.pose(viewer)); } @@ -96,12 +96,14 @@ public void tick(long time) { @Override public void updateNPC() { Pos npcPosition = config.position(viewer); - if (!getPosition().asVec().equals(npcPosition.asVec())) { + if (!getPosition().samePoint(npcPosition)) { + Pos nextPosition = stepTowards(getPosition(), npcPosition, 0.85D); + teleport(nextPosition); String[] holograms = config.holograms(viewer); boolean overflowing = holograms[holograms.length - 1].length() > 16; float yOffset = overflowing ? -0.2f : 0.0f; - PlayerHolograms.relocateExternalPlayerHologram(holo, npcPosition.add(0, getEyeHeight() + 0.5f + yOffset, 0)); + PlayerHolograms.relocateExternalPlayerHologram(holo, nextPosition.add(0, getEyeHeight() + 0.5f + yOffset, 0)); } if (!getPose().equals(config.pose(viewer))) { @@ -116,4 +118,23 @@ public void updateNPC() { this.holograms = finalHolograms; } } + + private static Pos stepTowards(Pos current, Pos target, double maxDistance) { + double dx = target.x() - current.x(); + double dy = target.y() - current.y(); + double dz = target.z() - current.z(); + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (distance <= maxDistance || distance == 0D) { + return target; + } + + double multiplier = maxDistance / distance; + return new Pos( + current.x() + dx * multiplier, + current.y() + dy * multiplier, + current.z() + dz * multiplier, + target.yaw(), + target.pitch() + ); + } } diff --git a/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSave.java b/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSave.java index 47a195738..0280e61b8 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSave.java +++ b/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSave.java @@ -25,72 +25,88 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; public class ActionPlayerDataSave implements HypixelEventClass { + private static final String DATA_SAVE_FAILURE_MESSAGE = + "We couldn't safely save your player data during transfer. Please reconnect."; + @SneakyThrows @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = false, isAsync = true) public void run(PlayerDisconnectEvent event) { final HypixelPlayer player = (HypixelPlayer) event.getPlayer(); UUID uuid = player.getUuid(); + boolean saveSucceeded = false; ResourcePackManager packManager = HypixelConst.getResourcePackManager(); if (packManager != null) { packManager.getActivePack().onPlayerQuit(player); } - Logger.info("Saving Hypixel account data for: " + player.getUsername() + "..."); + try { + Logger.info("Saving Hypixel account data for: " + player.getUsername() + "..."); - HypixelDataHandler handler = player.getDataHandler(); + Optional handlerOptional = player.getOptionalDataHandler(); + if (handlerOptional.isEmpty()) { + throw new IllegalStateException("User " + uuid + " does not exist in HypixelDataHandler cache during save"); + } - // Run onSave callbacks for basic Hypixel functionality - handler.runOnSave(player); + HypixelDataHandler handler = handlerOptional.get(); - // Save Hypixel data to UserDatabase (account-wide data) - UserDatabase userDatabase = new UserDatabase(uuid); - userDatabase.saveData(handler); + // Run onSave callbacks for basic Hypixel functionality + handler.runOnSave(player); - // Sync all leaderboard-tracked datapoints for Hypixel data - syncLeaderboards(uuid, handler); + // Save Hypixel data to UserDatabase (account-wide data) + UserDatabase userDatabase = new UserDatabase(uuid); + userDatabase.saveData(handler); - // Remove from cache - HypixelDataHandler.userCache.remove(uuid); + // Sync all leaderboard-tracked datapoints for Hypixel data + syncLeaderboards(uuid, handler); - // Save additional game handlers - List> additionalHandlers = + // Save additional game handlers + List> additionalHandlers = HypixelConst.getTypeLoader().getAdditionalDataHandlers(); - for (Class handlerClass : additionalHandlers) { - GameDataHandler gameHandler = GameDataHandlerRegistry.get(handlerClass); - if (gameHandler == null) continue; + for (Class handlerClass : additionalHandlers) { + GameDataHandler gameHandler = GameDataHandlerRegistry.get(handlerClass); + if (gameHandler == null) continue; - DataHandler gameDataHandler = gameHandler.getHandler(uuid); - if (gameDataHandler == null) continue; + DataHandler gameDataHandler = gameHandler.getHandler(uuid); + if (gameDataHandler == null) continue; - Logger.info("Saving " + gameHandler.getHandlerId() + " data for: " + player.getUsername()); + Logger.info("Saving " + gameHandler.getHandlerId() + " data for: " + player.getUsername()); - gameDataHandler.runOnSave(player); - userDatabase.saveData(gameDataHandler); + gameDataHandler.runOnSave(player); + userDatabase.saveData(gameDataHandler); - // Sync leaderboards for this game handler - syncLeaderboards(uuid, gameDataHandler); + // Sync leaderboards for this game handler + syncLeaderboards(uuid, gameDataHandler); + } - gameHandler.removeFromCache(uuid); - } + saveSucceeded = true; + Logger.info("Successfully saved all data for: " + player.getUsername()); + } catch (Exception e) { + Logger.error(e, "Failed to save all data for: {}", player.getUsername()); + } finally { + HypixelDataHandler.userCache.remove(uuid); - // Notify proxy that we're done with this player - ServerOutboundMessage.sendMessageToProxy( - ToProxyChannels.FINISHED_WITH_PLAYER, - new JSONObject().put("uuid", uuid.toString()), - (response) -> {} - ); + List> additionalHandlers = + HypixelConst.getTypeLoader().getAdditionalDataHandlers(); + for (Class handlerClass : additionalHandlers) { + GameDataHandler gameHandler = GameDataHandlerRegistry.get(handlerClass); + if (gameHandler != null) { + gameHandler.removeFromCache(uuid); + } + } - // Clean up tablist entries - MathUtility.delay(() -> { - HypixelConst.getTypeLoader().getTablistManager().deleteTablistEntries(player); - }, 5); + notifyProxyOfSaveCompletion(uuid, saveSucceeded); - Logger.info("Successfully saved all data for: " + player.getUsername()); + // Clean up tablist entries + MathUtility.delay(() -> { + HypixelConst.getTypeLoader().getTablistManager().deleteTablistEntries(player); + }, 5); + } } /** @@ -122,4 +138,20 @@ private void syncLeaderboards(UUID uuid, DataHandler handler) { } } } -} \ No newline at end of file + + private void notifyProxyOfSaveCompletion(UUID uuid, boolean saveSucceeded) { + JSONObject message = new JSONObject() + .put("uuid", uuid.toString()) + .put("success", saveSucceeded); + if (!saveSucceeded) { + message.put("reason", DATA_SAVE_FAILURE_MESSAGE); + } + + ServerOutboundMessage.sendMessageToProxy( + ToProxyChannels.FINISHED_WITH_PLAYER, + message, + (response) -> { + } + ); + } +} diff --git a/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSpawn.java b/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSpawn.java index 983647724..0ee3f0961 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSpawn.java +++ b/type.generic/src/main/java/net/swofty/type/generic/event/actions/data/ActionPlayerDataSpawn.java @@ -7,10 +7,10 @@ import net.swofty.type.generic.data.HypixelDataHandler; import net.swofty.type.generic.entity.hologram.PlayerHolograms; import net.swofty.type.generic.entity.npc.HypixelNPC; -import net.swofty.type.generic.resourcepack.ResourcePackManager; import net.swofty.type.generic.event.EventNodes; import net.swofty.type.generic.event.HypixelEvent; import net.swofty.type.generic.event.HypixelEventClass; +import net.swofty.type.generic.resourcepack.ResourcePackManager; import net.swofty.type.generic.user.HypixelPlayer; import java.util.List; @@ -46,7 +46,7 @@ public void run(PlayerSpawnEvent event) { } HypixelNPC.updateForPlayer(player); - if (HypixelConst.isIslandServer()) return; + if (HypixelConst.isIslandServer() || HypixelConst.isGarden()) return; PlayerHolograms.spawnAll(player); } } \ No newline at end of file diff --git a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryClose.java b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryClose.java index aa1a08285..183940c26 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryClose.java +++ b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryClose.java @@ -15,6 +15,9 @@ public void onPlayerInventoryClose(InventoryCloseEvent event) { HypixelPlayer player = (HypixelPlayer) event.getPlayer(); ViewNavigator.find(player).ifPresent(navigator -> { ViewSession session = navigator.getCurrentSession(); + if (session == null) { + return; + } if (event.getInventory() != session.inventory() || session.isSuppressCloseEvent()) { session.setSuppressCloseEvent(false); return; @@ -23,4 +26,4 @@ public void onPlayerInventoryClose(InventoryCloseEvent event) { }); } -} +} \ No newline at end of file diff --git a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryOpen.java b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryOpen.java index 075f5285d..de70b4c71 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryOpen.java +++ b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryOpen.java @@ -7,6 +7,7 @@ import net.swofty.type.generic.event.HypixelEventClass; import net.swofty.type.generic.gui.v2.ViewNavigator; import net.swofty.type.generic.user.HypixelPlayer; +import org.tinylog.Logger; public class ActionInventoryOpen implements HypixelEventClass { @@ -14,9 +15,15 @@ public class ActionInventoryOpen implements HypixelEventClass { public void onPlayerInventoryOpen(InventoryOpenEvent event) { MinecraftServer.getSchedulerManager().scheduleNextTick(() -> { HypixelPlayer player = (HypixelPlayer) event.getPlayer(); - ViewNavigator.find(player).ifPresent(navigator -> navigator.getCurrentSession().onOpenEvent(event)); + ViewNavigator.find(player).ifPresent(navigator -> { + if (navigator.getCurrentSession() == null) { + Logger.warn("Current session is null for player {} when opening inventory, this should not happen.", player.getUsername()); + return; + } + navigator.getCurrentSession().onOpenEvent(event); + }); }); } -} +} \ No newline at end of file diff --git a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPostClick.java b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPostClick.java index 40e0029cd..5d111577e 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPostClick.java +++ b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPostClick.java @@ -12,7 +12,12 @@ public class ActionInventoryPostClick implements HypixelEventClass { @HypixelEvent(node = EventNodes.INVENTORY, requireDataLoaded = false) public void onInventoryPostClick(InventoryClickEvent event) { HypixelPlayer player = (HypixelPlayer) event.getPlayer(); - ViewNavigator.find(player).ifPresent(navigator -> navigator.getCurrentSession().onPostClickEvent(event)); + ViewNavigator.find(player).ifPresent(navigator -> { + if (navigator.getCurrentSession() == null) { + return; + } + navigator.getCurrentSession().onPostClickEvent(event); + }); } -} +} \ No newline at end of file diff --git a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPreClick.java b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPreClick.java index 81343f72b..f5f2d5bd5 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPreClick.java +++ b/type.generic/src/main/java/net/swofty/type/generic/gui/v2/event/ActionInventoryPreClick.java @@ -13,8 +13,11 @@ public class ActionInventoryPreClick implements HypixelEventClass { public void onActionPlayerInventoryPreClick(InventoryPreClickEvent event) { HypixelPlayer player = (HypixelPlayer) event.getPlayer(); ViewNavigator.find(player).ifPresent(navigator -> { + if (navigator.getCurrentSession() == null) { + return; + } navigator.getCurrentSession().onPreClickEvent(event); }); } -} +} \ No newline at end of file diff --git a/type.generic/src/main/java/net/swofty/type/generic/scoreboard/HypixelScoreboard.java b/type.generic/src/main/java/net/swofty/type/generic/scoreboard/HypixelScoreboard.java index e13c97304..bad9b6170 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/scoreboard/HypixelScoreboard.java +++ b/type.generic/src/main/java/net/swofty/type/generic/scoreboard/HypixelScoreboard.java @@ -1,40 +1,48 @@ package net.swofty.type.generic.scoreboard; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.minestom.server.entity.Player; import net.minestom.server.scoreboard.Sidebar; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; public class HypixelScoreboard { + private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacySection(); private final Map sidebarCache = new HashMap<>(); - private final Map> lineCache = new HashMap<>(); + private final Map> lineCache = new HashMap<>(); private static String lineId(int index) { return "line_" + index; } - public void createScoreboard(Player player, String title) { + public void createScoreboard(Player player, Component title) { if (sidebarCache.containsKey(player.getUuid())) return; - Sidebar sidebar = new Sidebar(Component.text(title)); + Sidebar sidebar = new Sidebar(title); sidebar.addViewer(player); sidebarCache.put(player.getUuid(), sidebar); lineCache.put(player.getUuid(), new ArrayList<>()); } - public void updateTitle(Player player, String title) { + public void updateTitle(Player player, Component title) { Sidebar sidebar = sidebarCache.get(player.getUuid()); if (sidebar == null) return; - sidebar.setTitle(Component.text(title)); + sidebar.setTitle(title); } - public void updateLines(Player player, List lines) { + public void updateLines(Player player, List lines) { Sidebar sidebar = sidebarCache.get(player.getUuid()); if (sidebar == null) return; - List cached = lineCache.getOrDefault(player.getUuid(), new ArrayList<>()); + List cached = lineCache.getOrDefault(player.getUuid(), new ArrayList<>()); if (cached.equals(lines)) return; int oldCount = cached.size(); @@ -43,7 +51,7 @@ public void updateLines(Player player, List lines) { for (int i = 0; i < commonCount; i++) { if (!cached.get(i).equals(lines.get(i))) { - sidebar.updateLineContent(lineId(i), Component.text(lines.get(i))); + sidebar.updateLineContent(lineId(i), lines.get(i)); } int oldScore = oldCount - 1 - i; int newScore = newCount - 1 - i; @@ -54,9 +62,10 @@ public void updateLines(Player player, List lines) { for (int i = oldCount; i < newCount; i++) { sidebar.createLine(new Sidebar.ScoreboardLine( - lineId(i), - Component.text(lines.get(i)), - newCount - 1 - i + lineId(i), + lines.get(i), + newCount - 1 - i, + Sidebar.NumberFormat.blank() )); } @@ -82,4 +91,79 @@ public boolean hasScoreboard(Player player) { public Sidebar getSidebar(Player player) { return sidebarCache.get(player.getUuid()); } + + public static Component getSidebarName(String baseText, int counter, boolean guest) { + Component suffix = guest + ? Component.text(" GUEST", NamedTextColor.GREEN, TextDecoration.BOLD) + : Component.empty(); + + if (baseText == null || baseText.isEmpty()) { + return suffix; + } + + int highlightedIndex = Math.max(0, Math.min(baseText.length() - 1, counter - 1)); + if (counter > 0 && counter <= baseText.length()) { + return Component.empty() + .append(Component.text(baseText.substring(0, highlightedIndex), NamedTextColor.WHITE, TextDecoration.BOLD)) + .append(Component.text(String.valueOf(baseText.charAt(highlightedIndex)), NamedTextColor.GOLD, TextDecoration.BOLD)) + .append(Component.text(baseText.substring(highlightedIndex + 1), NamedTextColor.YELLOW, TextDecoration.BOLD)) + .append(suffix); + } + + if ((counter >= baseText.length() + 1 && counter <= baseText.length() + 11) + || (counter >= baseText.length() + 17 && counter <= baseText.length() + 21)) { + return Component.empty() + .append(Component.text(baseText, NamedTextColor.WHITE, TextDecoration.BOLD)) + .append(suffix); + } + + return Component.empty() + .append(Component.text(baseText, NamedTextColor.YELLOW, TextDecoration.BOLD)) + .append(suffix); + } + + public static Component legacy(String text) { + return LEGACY.deserialize(text == null ? "" : text); + } + + public static Component animatedSidebarName( + String baseText, + int counter, + NamedTextColor prefixColor, + NamedTextColor highlightColor, + NamedTextColor suffixColor, + NamedTextColor steadyColor, + NamedTextColor fallbackColor, + int highlightEndInclusive, + int steadyStartOne, + int steadyEndOne, + int steadyStartTwo, + int steadyEndTwo, + Component ending + ) { + Component suffix = ending == null ? Component.empty() : ending; + if (baseText == null || baseText.isEmpty()) { + return suffix; + } + + if (counter > 0 && counter <= highlightEndInclusive && counter <= baseText.length()) { + int index = counter - 1; + return Component.empty() + .append(Component.text(baseText.substring(0, index), prefixColor, TextDecoration.BOLD)) + .append(Component.text(String.valueOf(baseText.charAt(index)), highlightColor, TextDecoration.BOLD)) + .append(Component.text(baseText.substring(index + 1), suffixColor, TextDecoration.BOLD)) + .append(suffix); + } + + if ((counter >= steadyStartOne && counter <= steadyEndOne) + || (steadyStartTwo >= 0 && counter >= steadyStartTwo && counter <= steadyEndTwo)) { + return Component.empty() + .append(Component.text(baseText, steadyColor, TextDecoration.BOLD)) + .append(suffix); + } + + return Component.empty() + .append(Component.text(baseText, fallbackColor, TextDecoration.BOLD)) + .append(suffix); + } } diff --git a/type.generic/src/main/java/net/swofty/type/generic/user/HypixelPlayer.java b/type.generic/src/main/java/net/swofty/type/generic/user/HypixelPlayer.java index f2a2ad78a..2ea3e44f0 100644 --- a/type.generic/src/main/java/net/swofty/type/generic/user/HypixelPlayer.java +++ b/type.generic/src/main/java/net/swofty/type/generic/user/HypixelPlayer.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Objects; +import java.util.Optional; import java.util.UUID; public class HypixelPlayer extends Player { @@ -79,6 +80,14 @@ public HypixelDataHandler getDataHandler() { return HypixelDataHandler.getUser(this.getUuid()); } + public Optional getOptionalDataHandler() { + try { + return Optional.of(getDataHandler()); + } catch (RuntimeException e) { + return Optional.empty(); + } + } + public Rank getRank() { return getDataHandler().get(HypixelDataHandler.Data.RANK, DatapointRank.class).getValue(); } diff --git a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCGoldForger.java b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCGoldForger.java index f43dcb79d..321d117af 100644 --- a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCGoldForger.java +++ b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCGoldForger.java @@ -4,13 +4,12 @@ import net.swofty.type.generic.data.datapoints.DatapointToggles; 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.goldmine.gui.GUIShopGoldForger; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class NPCGoldForger extends HypixelNPC { +public class NPCGoldForger extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCGoldForger() { super(new HumanConfiguration() { @@ -67,4 +66,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build(), }; } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "GOLD_FORGER"; + } +} diff --git a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCIronForger.java b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCIronForger.java index a94cabb9d..50847123a 100644 --- a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCIronForger.java +++ b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCIronForger.java @@ -4,14 +4,12 @@ import net.swofty.type.generic.data.datapoints.DatapointToggles; 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.goldmine.gui.GUIShopGoldForger; import net.swofty.type.goldmine.gui.GUIShopIronForger; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class NPCIronForger extends HypixelNPC { +public class NPCIronForger extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCIronForger() { super(new HumanConfiguration() { @@ -69,4 +67,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build(), }; } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "IRON_FORGER"; + } +} diff --git a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCLazyMiner.java b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCLazyMiner.java index 3c0a0d74a..5fc8097db 100644 --- a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCLazyMiner.java +++ b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/NPCLazyMiner.java @@ -4,6 +4,7 @@ import net.swofty.type.generic.data.datapoints.DatapointToggles; 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.calendar.SkyBlockCalendar; import net.swofty.type.skyblockgeneric.mission.MissionData; @@ -14,9 +15,7 @@ import java.util.stream.Stream; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class NPCLazyMiner extends HypixelNPC { +public class NPCLazyMiner extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCLazyMiner() { super(new HumanConfiguration() { @@ -133,4 +132,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "LAZY_MINER"; + } +} diff --git a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/villagers/VillagerRusty.java b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/villagers/VillagerRusty.java index 90ec5556d..798054760 100644 --- a/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/villagers/VillagerRusty.java +++ b/type.goldmine/src/main/java/net/swofty/type/goldmine/npcs/villagers/VillagerRusty.java @@ -4,8 +4,9 @@ import net.minestom.server.entity.VillagerProfession; import net.swofty.type.generic.data.datapoints.DatapointToggles; import net.swofty.type.generic.entity.npc.HypixelNPC; -import net.swofty.type.generic.entity.npc.trait.NPCAbiphoneTrait; import net.swofty.type.generic.entity.npc.configuration.VillagerConfiguration; +import net.swofty.type.generic.entity.npc.trait.NPCAbiphoneTrait; +import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.skyblockgeneric.gui.inventories.rusty.GUIRusty; import net.swofty.type.skyblockgeneric.mission.missions.goldmine.lazyminer.MissionFindLazyMinerPickaxe; @@ -13,9 +14,7 @@ import java.util.stream.Stream; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class VillagerRusty extends HypixelNPC implements NPCAbiphoneTrait { +public class VillagerRusty extends HypixelNPC implements NPCAbiphoneTrait, net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public VillagerRusty() { super(new VillagerConfiguration() { @Override @@ -105,4 +104,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { public String getAbiphoneKey() { return "rusty"; } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "RUSTY"; + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/TypeHubLoader.java b/type.hub/src/main/java/net/swofty/type/hub/TypeHubLoader.java index 66ef9ec26..fbddad075 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/TypeHubLoader.java +++ b/type.hub/src/main/java/net/swofty/type/hub/TypeHubLoader.java @@ -134,6 +134,7 @@ public void afterInitialize(MinecraftServer server) { Furniture.load("hexatorum"); Furniture.load("rune_table"); + Furniture.load("swords_hub", new Pos(-52.5, 69.25, -85.5)); } @Override diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAdventurer.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAdventurer.java index 547562108..1b96a8328 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAdventurer.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAdventurer.java @@ -5,11 +5,10 @@ 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.i18n.I18n; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.hub.gui.GUIShopAdventurer; -public class NPCAdventurer extends HypixelNPC { +public class NPCAdventurer extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCAdventurer() { super(new HumanConfiguration() { @Override @@ -61,4 +60,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { DialogueSet.ofTranslation("hello", "npcs_hub.adventurer.dialogue.hello") }; } + + @Override + public String gardenSpokenNpcId() { + return "ADVENTURER"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAlchemist.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAlchemist.java index f01b5676c..d9e47d01a 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAlchemist.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAlchemist.java @@ -6,14 +6,13 @@ import net.swofty.type.generic.entity.npc.HypixelNPC; import net.swofty.type.generic.entity.npc.configuration.AnimalConfiguration; import net.swofty.type.generic.event.custom.NPCInteractEvent; -import net.swofty.type.generic.i18n.I18n; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.hub.gui.GUIShopAlchemist; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; import java.util.Map; -public class NPCAlchemist extends HypixelNPC { +public class NPCAlchemist extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCAlchemist() { super(new AnimalConfiguration() { @Override @@ -66,4 +65,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { DialogueSet.ofTranslation("hello", "npcs_hub.alchemist.dialogue.hello", Map.of("player", player.getUsername())) }; } + + @Override + public String gardenSpokenNpcId() { + return "ALCHEMIST"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAnita.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAnita.java index 7cfbc3d59..004fe7360 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAnita.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCAnita.java @@ -6,7 +6,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCAnita extends HypixelNPC { +public class NPCAnita extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCAnita() { super(new HumanConfiguration() { @@ -42,4 +42,8 @@ public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + @Override + public String gardenSpokenNpcId() { + return "ANITA"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCArthur.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCArthur.java index dd030b8b2..43a1ce07c 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCArthur.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCArthur.java @@ -4,12 +4,9 @@ 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.i18n.I18n; import net.swofty.type.generic.user.HypixelPlayer; -import java.util.stream.Stream; - -public class NPCArthur extends HypixelNPC { +public class NPCArthur extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCArthur() { super(new HumanConfiguration() { @@ -59,4 +56,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { DialogueSet.ofTranslation("dialogue-7", "npcs_hub.arthur.dialogue.dialogue_7") }; } + + @Override + public String gardenSpokenNpcId() { + return "ARTHUR"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCBartender.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCBartender.java index 581e99441..0d8787746 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCBartender.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCBartender.java @@ -4,16 +4,13 @@ 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.i18n.I18n; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.hub.gui.GUIShopBartender; import net.swofty.type.skyblockgeneric.mission.missions.MissionKillZombies; import net.swofty.type.skyblockgeneric.mission.missions.MissionTalkToBartender; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -import java.util.stream.Stream; - -public class NPCBartender extends HypixelNPC { +public class NPCBartender extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCBartender() { super(new HumanConfiguration() { @@ -75,4 +72,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { DialogueSet.ofTranslation("quest-complete", "npcs_hub.bartender.dialogue.quest_complete") }; } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "BARTENDER"; + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCClerkSeraphine.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCClerkSeraphine.java index c45bd0fd0..4473b5903 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCClerkSeraphine.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCClerkSeraphine.java @@ -8,7 +8,7 @@ import java.util.stream.Stream; -public class NPCClerkSeraphine extends HypixelNPC { +public class NPCClerkSeraphine extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCClerkSeraphine() { super(new HumanConfiguration() { @@ -56,4 +56,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "CLERK_SERAPHINE"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCDusk.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCDusk.java index 98bb6abce..961929945 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCDusk.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCDusk.java @@ -8,7 +8,7 @@ import java.util.stream.Stream; -public class NPCDusk extends HypixelNPC { +public class NPCDusk extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCDusk() { super(new HumanConfiguration() { @@ -55,4 +55,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "DUSK"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCFishermanGerald.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCFishermanGerald.java index b49e95aec..51077383f 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCFishermanGerald.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCFishermanGerald.java @@ -10,7 +10,7 @@ import java.util.stream.Stream; -public class NPCFishermanGerald extends HypixelNPC { +public class NPCFishermanGerald extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCFishermanGerald() { super(new HumanConfiguration() { @@ -72,4 +72,9 @@ protected DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } -} \ No newline at end of file + + @Override + public String gardenSpokenNpcId() { + return "FISHERMAN_GERALD"; + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCGuy.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCGuy.java index 717463c13..69f326bbc 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCGuy.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCGuy.java @@ -6,7 +6,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCGuy extends HypixelNPC { +public class NPCGuy extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCGuy() { super(new HumanConfiguration() { @@ -42,4 +42,8 @@ public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + @Override + public String gardenSpokenNpcId() { + return "GUY"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCJacob.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCJacob.java index 87190a387..210d2d581 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCJacob.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCJacob.java @@ -6,7 +6,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCJacob extends HypixelNPC { +public class NPCJacob extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCJacob() { super(new HumanConfiguration() { @@ -42,4 +42,8 @@ public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + @Override + public String gardenSpokenNpcId() { + return "JACOB"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCLumberJack.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCLumberJack.java index d7283675b..849a1974c 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCLumberJack.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCLumberJack.java @@ -17,7 +17,7 @@ import java.util.List; -public class NPCLumberJack extends HypixelNPC { +public class NPCLumberJack extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCLumberJack() { super(new HumanConfiguration() { @@ -138,4 +138,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "LUMBER_JACK"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCMadameEleanorQGoldsworthIII.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCMadameEleanorQGoldsworthIII.java index f504172c0..7f3919f62 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCMadameEleanorQGoldsworthIII.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCMadameEleanorQGoldsworthIII.java @@ -11,7 +11,7 @@ import java.util.stream.Stream; -public class NPCMadameEleanorQGoldsworthIII extends HypixelNPC { +public class NPCMadameEleanorQGoldsworthIII extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCMadameEleanorQGoldsworthIII() { super(new HumanConfiguration() { @@ -81,4 +81,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "MADAME_ELEANOR_Q_GOLDSWORTH_III"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSeymour.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSeymour.java index 7b8db8379..1ec066645 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSeymour.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSeymour.java @@ -9,7 +9,7 @@ import net.swofty.type.hub.gui.GUISeymour; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCSeymour extends HypixelNPC { +public class NPCSeymour extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCSeymour() { super(new HumanConfiguration() { @@ -65,4 +65,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build(), }; } + + @Override + public String gardenSpokenNpcId() { + return "SEYMOUR"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCShifty.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCShifty.java index 66361fa0a..f78b7e7d7 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCShifty.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCShifty.java @@ -6,7 +6,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCShifty extends HypixelNPC { +public class NPCShifty extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCShifty() { super(new HumanConfiguration() { @@ -41,4 +41,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { } + + @Override + public String gardenSpokenNpcId() { + return "SHIFTY"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSirius.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSirius.java index 1a22e725f..d7a5781ce 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSirius.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCSirius.java @@ -5,6 +5,7 @@ import net.swofty.commons.skyblock.auctions.DarkAuctionPhase; 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.hub.gui.GUIDarkAuction; import net.swofty.type.skyblockgeneric.darkauction.DarkAuctionHandler; @@ -12,9 +13,7 @@ import java.util.stream.Stream; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class NPCSirius extends HypixelNPC { +public class NPCSirius extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { private static final long MINIMUM_COINS = 400_000; public NPCSirius() { @@ -120,4 +119,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "SIRIUS"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCTiaTheFairy.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCTiaTheFairy.java index fc3518bc7..f936054a4 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCTiaTheFairy.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCTiaTheFairy.java @@ -11,7 +11,7 @@ import java.util.stream.Stream; -public class NPCTiaTheFairy extends HypixelNPC { +public class NPCTiaTheFairy extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCTiaTheFairy() { super(new HumanConfiguration() { @Override @@ -68,4 +68,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "TIA_THE_FAIRY"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCVincent.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCVincent.java index f7abeb936..53c532ef8 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCVincent.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCVincent.java @@ -1,10 +1,16 @@ package net.swofty.type.hub.npcs; import net.minestom.server.coordinate.Pos; +import net.minestom.server.item.Material; +import net.swofty.commons.skyblock.item.ItemType; 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.progression.GardenProgressionReward; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSupport; +import net.swofty.type.skyblockgeneric.item.SkyBlockItem; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; public class NPCVincent extends HypixelNPC { @@ -39,6 +45,20 @@ public boolean looking(HypixelPlayer player) { @Override public void onClick(NPCInteractEvent event) { - + SkyBlockPlayer player = (SkyBlockPlayer) event.player(); + SkyBlockItem heldItem = new SkyBlockItem(player.getItemInMainHand()); + ItemType heldType = heldItem.getAttributeHandler().getPotentialType(); + boolean dyeItem = heldType != null + ? heldType.name().endsWith("_DYE") + : heldItem.getMaterial().name().endsWith("_DYE") || heldItem.getMaterial() == Material.INK_SAC; + if (dyeItem) { + if (heldType != null) { + player.takeItem(heldType, 1); + } + GardenProgressionSupport.apply(player, GardenProgressionReward.donatedItem("DYE")); + sendNPCMessage(player, "A lovely color choice. I'll remember your donation."); + return; + } + sendNPCMessage(player, "Bring me any dye if you want to trade colors with the Garden."); } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWeaponsmith.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWeaponsmith.java index 543f7f7ff..b9d26bab1 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWeaponsmith.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWeaponsmith.java @@ -9,7 +9,7 @@ import net.swofty.type.hub.gui.GUIShopWeaponsmith; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCWeaponsmith extends HypixelNPC { +public class NPCWeaponsmith extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCWeaponsmith() { super(new HumanConfiguration() { @Override @@ -65,4 +65,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build(), }; } + + @Override + public String gardenSpokenNpcId() { + return "WEAPONSMITH"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWizard.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWizard.java index 52a828f02..591a76e9a 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWizard.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCWizard.java @@ -8,7 +8,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCWizard extends HypixelNPC { +public class NPCWizard extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCWizard() { super(new HumanConfiguration() { @@ -44,4 +44,8 @@ public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + @Override + public String gardenSpokenNpcId() { + return "WIZARD"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCZog.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCZog.java index 0c3c72dc7..1953477f2 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCZog.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCZog.java @@ -9,7 +9,7 @@ import net.swofty.type.hub.gui.GUIShopZog; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; -public class NPCZog extends HypixelNPC { +public class NPCZog extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCZog() { super(new HumanConfiguration() { @@ -67,4 +67,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build(), }; } + + @Override + public String gardenSpokenNpcId() { + return "ZOG"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerLibrarian.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerLibrarian.java index 1c80d474a..f9069be8f 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerLibrarian.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerLibrarian.java @@ -12,7 +12,7 @@ import java.util.stream.Stream; -public class VillagerLibrarian extends HypixelNPC { +public class VillagerLibrarian extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public VillagerLibrarian() { super(new VillagerConfiguration(){ @Override @@ -66,4 +66,9 @@ public DialogueSet[] dialogues(HypixelPlayer player) { }).build() ).toArray(DialogueSet[]::new); } + + @Override + public String gardenSpokenNpcId() { + return "LIBRARIAN"; + } } diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerPlumberJoe.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerPlumberJoe.java index 8664195a1..43f56aac0 100644 --- a/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerPlumberJoe.java +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/villagers/VillagerPlumberJoe.java @@ -7,7 +7,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class VillagerPlumberJoe extends HypixelNPC { +public class VillagerPlumberJoe extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public VillagerPlumberJoe() { super(new VillagerConfiguration() { @Override @@ -36,4 +36,9 @@ public VillagerProfession profession() { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "PLUMBER_JOE"; + } } diff --git a/type.jerrysworkshop/src/main/java/net/swofty/type/jerrysworkshop/npcs/NPCTerry.java b/type.jerrysworkshop/src/main/java/net/swofty/type/jerrysworkshop/npcs/NPCTerry.java index e22e7dde1..35294a399 100644 --- a/type.jerrysworkshop/src/main/java/net/swofty/type/jerrysworkshop/npcs/NPCTerry.java +++ b/type.jerrysworkshop/src/main/java/net/swofty/type/jerrysworkshop/npcs/NPCTerry.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.entity.npc.configuration.NPCConfiguration; import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCTerry extends HypixelNPC { +public class NPCTerry extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCTerry() { super(new HumanConfiguration() { @@ -42,4 +41,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { } + + @Override + public String gardenSpokenNpcId() { + return "TERRY"; + } } diff --git a/type.murdermysterygame/src/main/java/net/swofty/type/murdermysterygame/MurderMysteryGameScoreboard.java b/type.murdermysterygame/src/main/java/net/swofty/type/murdermysterygame/MurderMysteryGameScoreboard.java index 8ea1cbb0c..38f58fc72 100644 --- a/type.murdermysterygame/src/main/java/net/swofty/type/murdermysterygame/MurderMysteryGameScoreboard.java +++ b/type.murdermysterygame/src/main/java/net/swofty/type/murdermysterygame/MurderMysteryGameScoreboard.java @@ -6,13 +6,13 @@ import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; import net.minestom.server.timer.TaskSchedule; +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.i18n.I18n; +import net.swofty.type.generic.scoreboard.HypixelScoreboard; import net.swofty.type.murdermysterygame.game.Game; import net.swofty.type.murdermysterygame.game.GameStatus; import net.swofty.type.murdermysterygame.role.GameRole; import net.swofty.type.murdermysterygame.user.MurderMysteryPlayer; -import net.swofty.type.generic.HypixelConst; -import net.swofty.type.generic.i18n.I18n; -import net.swofty.type.generic.scoreboard.HypixelScoreboard; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -38,21 +38,21 @@ public static void start() { for (MurderMysteryPlayer player : game.getPlayers()) { if (player.getInstance() == null) continue; - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); if (game.getGameStatus() == GameStatus.WAITING) { - lines.add(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName()); - lines.add(I18n.string("scoreboard.murdermystery_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers()); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName())); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers())); + lines.add(HypixelScoreboard.legacy("§7 ")); if (game.getCountdown().isActive()) { - lines.add(I18n.string("scoreboard.murdermystery_game.starting_in_label") + game.getCountdown().getSecondsRemaining() + I18n.string("scoreboard.murdermystery_game.starting_in_suffix")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.starting_in_label") + game.getCountdown().getSecondsRemaining() + I18n.string("scoreboard.murdermystery_game.starting_in_suffix"))); } else { - lines.add(I18n.string("scoreboard.murdermystery_game.waiting_for_players")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.waiting_for_players"))); } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.murdermystery_game.mode_label") + game.getGameType().getDisplayName()); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.mode_label") + game.getGameType().getDisplayName())); int playerCount = game.getPlayers().size(); int murdererChance = playerCount > 0 ? Math.round(100f / playerCount) : 0; @@ -66,79 +66,79 @@ public static void start() { GameRole role = game.getRoleManager().getRole(player.getUuid()); if (player.isEliminated()) { - lines.add(I18n.string("scoreboard.murdermystery_game.spectating_label")); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.spectating_label"))); + lines.add(HypixelScoreboard.legacy("§7 ")); if (role != null) { - lines.add(I18n.string("scoreboard.murdermystery_game.your_role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.your_role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName())); } - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy("§7 ")); int playersAlive = game.getRoleManager().countAliveWithRole(GameRole.INNOCENT) + game.getRoleManager().countAliveWithRole(GameRole.DETECTIVE) + game.getRoleManager().countAliveWithRole(GameRole.MURDERER); - lines.add(I18n.string("scoreboard.murdermystery_game.players_alive_label") + playersAlive); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.players_alive_label") + playersAlive)); String timeLeft = formatTimeRemaining(game.getGameStartTime()); - lines.add(I18n.string("scoreboard.murdermystery_game.time_left_label") + timeLeft); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.time_left_label") + timeLeft)); + lines.add(HypixelScoreboard.legacy("§7 ")); boolean detectiveAlive = game.getRoleManager().countAliveWithRole(GameRole.DETECTIVE) > 0; String detectiveStatus = detectiveAlive ? I18n.string("scoreboard.murdermystery_game.detective_alive") : I18n.string("scoreboard.murdermystery_game.detective_dead"); - lines.add(I18n.string("scoreboard.murdermystery_game.detective_label") + " " + detectiveStatus); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.detective_label") + " " + detectiveStatus)); + lines.add(HypixelScoreboard.legacy("§7 ")); - lines.add(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName())); } else { if (role != null) { - lines.add(I18n.string("scoreboard.murdermystery_game.role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName())); } - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy("§7 ")); int innocentsLeft = game.getRoleManager().countAliveWithRole(GameRole.INNOCENT) + game.getRoleManager().countAliveWithRole(GameRole.DETECTIVE); - lines.add(I18n.string("scoreboard.murdermystery_game.innocents_left_label") + innocentsLeft); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.innocents_left_label") + innocentsLeft)); String timeLeft = formatTimeRemaining(game.getGameStartTime()); - lines.add(I18n.string("scoreboard.murdermystery_game.time_left_label") + timeLeft); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.time_left_label") + timeLeft)); + lines.add(HypixelScoreboard.legacy("§7 ")); boolean detectiveAlive = game.getRoleManager().countAliveWithRole(GameRole.DETECTIVE) > 0; String detectiveStatus = detectiveAlive ? I18n.string("scoreboard.murdermystery_game.detective_alive") : I18n.string("scoreboard.murdermystery_game.detective_dead"); - lines.add(I18n.string("scoreboard.murdermystery_game.detective_label") + " " + detectiveStatus); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.detective_label") + " " + detectiveStatus)); + lines.add(HypixelScoreboard.legacy("§7 ")); - lines.add(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName())); } } else if (game.getGameStatus() == GameStatus.ENDING) { - lines.add(I18n.string("scoreboard.murdermystery_game.game_over")); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.game_over"))); + lines.add(HypixelScoreboard.legacy("§7 ")); GameRole role = game.getRoleManager().getRole(player.getUuid()); if (role != null) { - lines.add(I18n.string("scoreboard.murdermystery_game.your_role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.your_role_label") + " " + getScoreboardRoleColor(role) + role.getDisplayName())); } - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy("§7 ")); int kills = player.getKillsThisGame(); if (kills > 0) { - lines.add(I18n.string("scoreboard.murdermystery_game.your_kills_label") + kills); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.your_kills_label") + kills)); } int tokens = player.getTokensEarnedThisGame(); - lines.add(I18n.string("scoreboard.murdermystery_game.tokens_earned_label") + tokens); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.tokens_earned_label") + tokens)); + lines.add(HypixelScoreboard.legacy("§7 ")); - lines.add(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName()); - lines.add(I18n.string("scoreboard.murdermystery_game.mode_label") + game.getGameType().getDisplayName()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.map_label") + game.getMapEntry().getName())); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_game.mode_label") + game.getGameType().getDisplayName())); } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(animationFrame)); @@ -176,20 +176,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.murdermystery_game.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - - if (counter > 0 && counter <= 14) { - return colors[0] + baseText.substring(0, Math.min(counter - 1, baseText.length())) + - colors[1] + (counter <= baseText.length() ? String.valueOf(baseText.charAt(counter - 1)) : "") + - colors[2] + (counter < baseText.length() ? baseText.substring(counter) : "") + - endColor; - } else if ((counter >= 15 && counter <= 25) || (counter >= 35 && counter <= 45)) { - return colors[0] + baseText + endColor; - } else { - return colors[2] + baseText + endColor; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.murdermystery_game.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.GOLD, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 14, + 15, + 25, + 35, + 45, + Component.text("MYSTERY", NamedTextColor.GREEN, net.kyori.adventure.text.format.TextDecoration.BOLD) + ); } } diff --git a/type.murdermysterylobby/src/main/java/net/swofty/type/murdermysterylobby/MurderMysteryLobbyScoreboard.java b/type.murdermysterylobby/src/main/java/net/swofty/type/murdermysterylobby/MurderMysteryLobbyScoreboard.java index 01b941cf7..1a8b613e4 100644 --- a/type.murdermysterylobby/src/main/java/net/swofty/type/murdermysterylobby/MurderMysteryLobbyScoreboard.java +++ b/type.murdermysterylobby/src/main/java/net/swofty/type/murdermysterylobby/MurderMysteryLobbyScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.murdermysterylobby; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -56,18 +58,18 @@ public static void start() { tokens = stats.getTotalTokens(MurderMysteryLeaderboardPeriod.LIFETIME); } - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.murdermystery_lobby.total_kills_label") + totalKills); - lines.add(I18n.string("scoreboard.murdermystery_lobby.total_wins_label") + totalWins); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.murdermystery_lobby.wins_as_detective_label") + detectiveWins); - lines.add(I18n.string("scoreboard.murdermystery_lobby.wins_as_murderer_label") + murdererWins); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.murdermystery_lobby.tokens_label") + tokens); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_lobby.total_kills_label") + totalKills)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_lobby.total_wins_label") + totalWins)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_lobby.wins_as_detective_label") + detectiveWins)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_lobby.wins_as_murderer_label") + murdererWins)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.murdermystery_lobby.tokens_label") + tokens)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(animationFrame)); @@ -84,20 +86,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.murdermystery_lobby.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - - if (counter > 0 && counter <= 14) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter) + - endColor; - } else if ((counter >= 15 && counter <= 25) || (counter >= 35 && counter <= 45)) { - return colors[0] + baseText + endColor; - } else { - return colors[2] + baseText + endColor; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.murdermystery_lobby.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.GOLD, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 14, + 15, + 25, + 35, + 45, + Component.text("MYSTERY", NamedTextColor.GREEN, net.kyori.adventure.text.format.TextDecoration.BOLD) + ); } } diff --git a/type.prototypelobby/src/main/java/net/swofty/type/prototypelobby/PrototypeLobbyScoreboard.java b/type.prototypelobby/src/main/java/net/swofty/type/prototypelobby/PrototypeLobbyScoreboard.java index 076bc153c..4d54a2c5a 100644 --- a/type.prototypelobby/src/main/java/net/swofty/type/prototypelobby/PrototypeLobbyScoreboard.java +++ b/type.prototypelobby/src/main/java/net/swofty/type/prototypelobby/PrototypeLobbyScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.prototypelobby; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -17,6 +19,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Optional; public class PrototypeLobbyScoreboard { private static final HypixelScoreboard scoreboard = new HypixelScoreboard(); @@ -32,28 +35,28 @@ public static void start() { } for (HypixelPlayer player : HypixelGenericLoader.getLoadedPlayers()) { - HypixelDataHandler dataHandler = player.getDataHandler(); + Optional dataHandler = player.getOptionalDataHandler(); PrototypeLobbyDataHandler prototypeDataHandler = PrototypeLobbyDataHandler.getUser(player); - if (dataHandler == null || prototypeDataHandler == null) { + if (dataHandler.isEmpty() || prototypeDataHandler == null) { continue; } long hype = prototypeDataHandler.get(PrototypeLobbyDataHandler.Data.HYPE, DatapointLeaderboardLong.class).getValue(); - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.prototype_lobby.dev_notice_line1")); - lines.add(I18n.string("scoreboard.prototype_lobby.dev_notice_line2")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.prototype_lobby.bug_report_line1")); - lines.add(I18n.string("scoreboard.prototype_lobby.bug_report_line2")); - lines.add(I18n.string("scoreboard.prototype_lobby.bug_report_url")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.prototype_lobby.hype_label") + hype + I18n.string("scoreboard.prototype_lobby.hype_max")); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.dev_notice_line1"))); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.dev_notice_line2"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.bug_report_line1"))); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.bug_report_line2"))); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.bug_report_url"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.prototype_lobby.hype_label") + hype + I18n.string("scoreboard.prototype_lobby.hype_max"))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(prototypeName)); @@ -70,21 +73,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.prototype_lobby.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - - if (counter > 0 && counter <= 8) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter) + - endColor; - } else if ((counter >= 9 && counter <= 19) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText + endColor; - } else { - return colors[2] + baseText + endColor; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.prototype_lobby.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.GOLD, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 8, + 9, + 19, + 25, + 29, + Component.empty() + ); } } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/SkyBlockGenericLoader.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/SkyBlockGenericLoader.java index 9c1f868e7..356789b5e 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/SkyBlockGenericLoader.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/SkyBlockGenericLoader.java @@ -54,6 +54,7 @@ import net.swofty.type.skyblockgeneric.data.monogdb.CoopDatabase; import net.swofty.type.skyblockgeneric.data.monogdb.CrystalDatabase; import net.swofty.type.skyblockgeneric.data.monogdb.FairySoulDatabase; +import net.swofty.type.skyblockgeneric.data.monogdb.GardenDatabase; import net.swofty.type.skyblockgeneric.data.monogdb.IslandDatabase; import net.swofty.type.skyblockgeneric.data.monogdb.RegionDatabase; import net.swofty.type.skyblockgeneric.entity.ServerCrystalImpl; @@ -137,6 +138,7 @@ public void initialize(MinecraftServer server) { RegionDatabase.connect(mongoClient); IslandDatabase.connect(mongoClient); + GardenDatabase.connect(mongoClient); FairySoulDatabase.connect(mongoClient); CoopDatabase.connect(mongoClient); CrystalDatabase.connect(mongoClient); diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/BlockPlacementManager.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/BlockPlacementManager.java index a77c75d0d..acab6195e 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/BlockPlacementManager.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/BlockPlacementManager.java @@ -109,10 +109,7 @@ public enum BlockPlacementManager { ChestBlockPlacement::new ), WALL( - block -> { - String blockName = block.name().toLowerCase(); - return blockName.contains("wall") && !blockName.contains("skull") && !blockName.contains("torch") && !blockName.contains("head") && !blockName.contains("wall_sign"); - }, + BlockPlacementManager::isWallBlock, WallPlacement::new ), SLAB( @@ -156,6 +153,15 @@ public enum BlockPlacementManager { this.placementSupplier = placementSupplier; } + private static boolean isWallBlock(Block block) { + var properties = block.properties(); + return properties.containsKey("north") + && properties.containsKey("east") + && properties.containsKey("south") + && properties.containsKey("west") + && properties.containsKey("up"); + } + public static void register(Block block) { MultiplePlacementRules placementRules = new MultiplePlacementRules(block); @@ -173,4 +179,4 @@ public static void registerAll() { register(block); } } -} \ No newline at end of file +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/states/BlockStateManager.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/states/BlockStateManager.java index 42d769b71..a1e5868a5 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/states/BlockStateManager.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/block/placement/states/BlockStateManager.java @@ -19,10 +19,19 @@ else if (block.name().endsWith("stairs")) return new StairsState(block); else if (block.name().contains("fence_gate")) return new FenceGateState(block); - else if (block.name().contains("wall")) + else if (isWallBlock(block)) return new WallState(block); else if (block.properties().containsKey("axis")) return new AxisBlockBlockState(block); return new BlockState(block); } + + private static boolean isWallBlock(Block block) { + var properties = block.properties(); + return properties.containsKey("north") + && properties.containsKey("east") + && properties.containsKey("south") + && properties.containsKey("west") + && properties.containsKey("up"); + } } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/GardenCommand.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/GardenCommand.java new file mode 100644 index 000000000..74944eea5 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/GardenCommand.java @@ -0,0 +1,24 @@ +package net.swofty.type.skyblockgeneric.commands; + +import net.swofty.commons.ServerType; +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.user.SkyBlockPlayer; + +@CommandParameters(aliases = "gar", + description = "Sends the player to their Garden", + usage = "/garden", + permission = Rank.DEFAULT, + allowsConsole = false) +public class GardenCommand extends HypixelCommand { + @Override + public void registerUsage(MinestomCommand command) { + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + + SkyBlockPlayer player = (SkyBlockPlayer) sender; + player.sendTo(ServerType.SKYBLOCK_GARDEN); + }); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/SkyBlockDataHandler.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/SkyBlockDataHandler.java index 4b01c28b0..cd2a25b1b 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/SkyBlockDataHandler.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/SkyBlockDataHandler.java @@ -11,7 +11,14 @@ import net.swofty.commons.skyblock.item.ItemType; import net.swofty.type.generic.data.DataHandler; import net.swofty.type.generic.data.Datapoint; -import net.swofty.type.generic.data.datapoints.*; +import net.swofty.type.generic.data.datapoints.DatapointBoolean; +import net.swofty.type.generic.data.datapoints.DatapointDouble; +import net.swofty.type.generic.data.datapoints.DatapointInteger; +import net.swofty.type.generic.data.datapoints.DatapointLong; +import net.swofty.type.generic.data.datapoints.DatapointMapStringLong; +import net.swofty.type.generic.data.datapoints.DatapointPresentYear; +import net.swofty.type.generic.data.datapoints.DatapointString; +import net.swofty.type.generic.data.datapoints.DatapointStringList; import net.swofty.type.generic.data.mongodb.ProfilesDatabase; import net.swofty.type.generic.data.mongodb.UserDatabase; import net.swofty.type.generic.user.HypixelPlayer; @@ -30,7 +37,11 @@ import org.tinylog.Logger; import tools.jackson.core.JacksonException; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Function; @@ -311,7 +322,14 @@ DatapointSkillCategory.class, new DatapointSkillCategory("last_edited_skill", Sk ISLAND_UUID("island_uuid", false, true, false, DatapointUUID.class, new DatapointUUID("island_uuid", null), (player, datapoint) -> {}, - (player, datapoint) -> ((DatapointUUID) datapoint).setValue(player.getSkyBlockIsland().getIslandID())), + (player, datapoint) -> { + DatapointUUID islandUuid = (DatapointUUID) datapoint; + if (player.getSkyBlockIsland() != null) { + islandUuid.setValue(player.getSkyBlockIsland().getIslandID()); + } else if (islandUuid.getValue() == null && player.getProfiles() != null) { + islandUuid.setValue(player.getProfiles().getCurrentlySelected()); + } + }), IS_COOP("is_coop", false, true, false, DatapointBoolean.class, new DatapointBoolean("is_coop", false)), @@ -452,6 +470,24 @@ DatapointHOTM.class, new DatapointHOTM("hotm")), KAT("kat", false, false, false, DatapointKat.class, new DatapointKat("kat")), + GARDEN_CORE("garden_core", false, true, false, + DatapointGardenCore.class, new DatapointGardenCore("garden_core")), + + GARDEN_VISITORS("garden_visitors", false, true, false, + DatapointGardenVisitors.class, new DatapointGardenVisitors("garden_visitors")), + + GARDEN_PESTS("garden_pests", false, true, false, + DatapointGardenPests.class, new DatapointGardenPests("garden_pests")), + + GARDEN_COMPOSTER("garden_composter", false, true, false, + DatapointGardenComposter.class, new DatapointGardenComposter("garden_composter")), + + GARDEN_GREENHOUSE("garden_greenhouse", false, true, false, + DatapointGardenGreenhouse.class, new DatapointGardenGreenhouse("garden_greenhouse")), + + GARDEN_PERSONAL("garden_personal", false, false, false, + DatapointGardenPersonal.class, new DatapointGardenPersonal("garden_personal")), + STASH("stash", false, false, false, DatapointStash.class, new DatapointStash("stash")), @@ -512,4 +548,4 @@ public static void startRepeatSetValueLoop() { return TaskSchedule.nextTick(); }); } -} \ No newline at end of file +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenComposter.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenComposter.java new file mode 100644 index 000000000..a7e752396 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenComposter.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenComposter extends SkyBlockDatapoint { + public DatapointGardenComposter(String key, GardenData.GardenComposterData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenComposterData.class)); + } + + public DatapointGardenComposter(String key) { + this(key, new GardenData.GardenComposterData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenCore.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenCore.java new file mode 100644 index 000000000..fa22916dd --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenCore.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenCore extends SkyBlockDatapoint { + public DatapointGardenCore(String key, GardenData.GardenCoreData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenCoreData.class)); + } + + public DatapointGardenCore(String key) { + this(key, new GardenData.GardenCoreData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenGreenhouse.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenGreenhouse.java new file mode 100644 index 000000000..1eb26cfdd --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenGreenhouse.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenGreenhouse extends SkyBlockDatapoint { + public DatapointGardenGreenhouse(String key, GardenData.GardenGreenhouseData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenGreenhouseData.class)); + } + + public DatapointGardenGreenhouse(String key) { + this(key, new GardenData.GardenGreenhouseData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPersonal.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPersonal.java new file mode 100644 index 000000000..95bd97edb --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPersonal.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenPersonal extends SkyBlockDatapoint { + public DatapointGardenPersonal(String key, GardenData.GardenPersonalData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenPersonalData.class)); + } + + public DatapointGardenPersonal(String key) { + this(key, new GardenData.GardenPersonalData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPests.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPests.java new file mode 100644 index 000000000..ceba31c1c --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenPests.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenPests extends SkyBlockDatapoint { + public DatapointGardenPests(String key, GardenData.GardenPestsData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenPestsData.class)); + } + + public DatapointGardenPests(String key) { + this(key, new GardenData.GardenPestsData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenVisitors.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenVisitors.java new file mode 100644 index 000000000..bd048b11f --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointGardenVisitors.java @@ -0,0 +1,15 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import net.swofty.type.skyblockgeneric.data.serializer.RoundTripJsonSerializer; +import net.swofty.type.skyblockgeneric.garden.GardenData; + +public class DatapointGardenVisitors extends SkyBlockDatapoint { + public DatapointGardenVisitors(String key, GardenData.GardenVisitorsData value) { + super(key, value, new RoundTripJsonSerializer<>(GardenData.GardenVisitorsData.class)); + } + + public DatapointGardenVisitors(String key) { + this(key, new GardenData.GardenVisitorsData()); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/monogdb/GardenDatabase.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/monogdb/GardenDatabase.java new file mode 100644 index 000000000..d530d497b --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/monogdb/GardenDatabase.java @@ -0,0 +1,87 @@ +package net.swofty.type.skyblockgeneric.data.monogdb; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import net.swofty.type.generic.data.mongodb.MongoDB; +import org.bson.Document; + +import java.util.ArrayList; +import java.util.List; + +public record GardenDatabase(String profileUuid) implements MongoDB { + public static MongoDatabase database; + public static MongoCollection collection; + + public static void connect(MongoClient client) { + database = client.getDatabase("Minestom"); + collection = database.getCollection("garden"); + } + + @Override + public void set(String key, Object value) { + insertOrUpdate(key, value); + } + + @Override + public Object get(String key, Object def) { + Document doc = collection.find(Filters.eq("_id", profileUuid)).first(); + if (doc == null) { + return def; + } + return doc.get(key); + } + + public boolean has(String key) { + Document doc = collection.find(Filters.eq("_id", profileUuid)).first(); + return doc != null && doc.containsKey(key); + } + + public List getAll() { + FindIterable results = collection.find(); + List list = new ArrayList<>(); + for (Document doc : results) { + list.add(doc); + } + return list; + } + + public Document getDocument() { + Document query = new Document("_id", profileUuid); + return collection.find(query).first(); + } + + @Override + public boolean remove(String id) { + Document query = new Document("_id", id); + Document found = collection.find(query).first(); + if (found == null) { + return false; + } + collection.deleteOne(query); + return true; + } + + public void insertOrUpdate(String key, Object value) { + if (exists()) { + Document query = new Document("_id", profileUuid); + Document found = collection.find(query).first(); + assert found != null; + collection.updateOne(found, Updates.set(key, value)); + return; + } + + Document document = new Document("_id", profileUuid); + document.append(key, value); + collection.insertOne(document); + } + + public boolean exists() { + Document query = new Document("_id", profileUuid); + Document found = collection.find(query).first(); + return found != null; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/serializer/RoundTripJsonSerializer.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/serializer/RoundTripJsonSerializer.java new file mode 100644 index 000000000..e68558359 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/serializer/RoundTripJsonSerializer.java @@ -0,0 +1,40 @@ +package net.swofty.type.skyblockgeneric.data.serializer; + +import lombok.SneakyThrows; +import net.swofty.commons.protocol.Serializer; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; + +public class RoundTripJsonSerializer implements Serializer { + private static final JsonMapper MAPPER = JsonMapper.builder() + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + + private final Class clazz; + + public RoundTripJsonSerializer(Class clazz) { + this.clazz = clazz; + } + + @Override + @SneakyThrows + public String serialize(T value) { + return MAPPER.writeValueAsString(value); + } + + @Override + @SneakyThrows + public T deserialize(String json) { + return MAPPER.readValue(json, clazz); + } + + @Override + public T clone(T value) { + if (value == null) { + return null; + } + return deserialize(serialize(value)); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/entity/ActionChunkUnload.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/entity/ActionChunkUnload.java index fb549b543..140c7a4a8 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/entity/ActionChunkUnload.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/entity/ActionChunkUnload.java @@ -15,7 +15,7 @@ public void run(InstanceChunkUnloadEvent event) { int chunkX = event.getChunkX(); int chunkZ = event.getChunkZ(); - if (HypixelConst.isIslandServer()) return; + if (HypixelConst.isIslandServer() || HypixelConst.isGarden()) return; instance.loadChunk(chunkX, chunkZ).join(); } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/npc/ActionPlayerInteractNPC.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/npc/ActionPlayerInteractNPC.java index fd9e6bf8a..e91851e85 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/npc/ActionPlayerInteractNPC.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/npc/ActionPlayerInteractNPC.java @@ -8,6 +8,8 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.skyblockgeneric.abiphone.AbiphoneNPC; import net.swofty.type.skyblockgeneric.abiphone.AbiphoneRegistry; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSource; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSupport; import net.swofty.type.skyblockgeneric.item.SkyBlockItem; import net.swofty.type.skyblockgeneric.item.components.AbiphoneComponent; import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; @@ -19,6 +21,9 @@ public class ActionPlayerInteractNPC implements HypixelEventClass { public void run(NPCInteractEvent event) { final SkyBlockPlayer player = (SkyBlockPlayer) event.getPlayer(); HypixelNPC npc = event.getNpc(); + if (npc instanceof GardenProgressionSource source) { + source.gardenProgressionRewards(player).forEach(reward -> GardenProgressionSupport.apply(player, reward)); + } SkyBlockItem item = new SkyBlockItem(player.getItemInMainHand()); if (item.hasComponent(AbiphoneComponent.class)) { if (!(npc instanceof NPCAbiphoneTrait trait)) { @@ -39,5 +44,4 @@ public void run(NPCInteractEvent event) { event.setCancelled(true); } } - } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/blocks/ActionPlayerSetupMining.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/blocks/ActionPlayerSetupMining.java index 20f529237..a616c59d5 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/blocks/ActionPlayerSetupMining.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/blocks/ActionPlayerSetupMining.java @@ -1,18 +1,11 @@ package net.swofty.type.skyblockgeneric.event.actions.player.blocks; import net.minestom.server.entity.attribute.Attribute; -import net.minestom.server.event.player.PlayerBlockInteractEvent; import net.minestom.server.event.player.PlayerSpawnEvent; -import net.minestom.server.network.packet.server.play.EntityAttributesPacket; -import net.minestom.server.network.packet.server.play.EntityEffectPacket; -import net.minestom.server.potion.Potion; -import net.minestom.server.potion.PotionEffect; import net.swofty.type.generic.HypixelConst; import net.swofty.type.generic.event.EventNodes; import net.swofty.type.generic.event.HypixelEvent; import net.swofty.type.generic.event.HypixelEventClass; -import net.swofty.type.skyblockgeneric.block.SkyBlockBlock; -import net.swofty.type.skyblockgeneric.block.impl.BlockInteractable; public class ActionPlayerSetupMining implements HypixelEventClass { @@ -20,7 +13,7 @@ public class ActionPlayerSetupMining implements HypixelEventClass { public void onSpawn(PlayerSpawnEvent event) { var player = event.getPlayer(); - if (HypixelConst.isIslandServer()) return; + if (HypixelConst.isIslandServer() || HypixelConst.isGarden()) return; player.getAttribute(Attribute.BLOCK_BREAK_SPEED).clearModifiers(); player.getAttribute(Attribute.BLOCK_BREAK_SPEED).setBaseValue(0D); diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockBreak.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockBreak.java index 346b13229..48df6ce9c 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockBreak.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockBreak.java @@ -15,6 +15,7 @@ import net.swofty.type.generic.event.HypixelEventHandler; import net.swofty.type.skyblockgeneric.entity.DroppedItemEntityImpl; import net.swofty.type.skyblockgeneric.event.custom.CustomBlockBreakEvent; +import net.swofty.type.skyblockgeneric.garden.SkyBlockEditableWorldHandle; import net.swofty.type.skyblockgeneric.item.SkyBlockItem; import net.swofty.type.skyblockgeneric.item.components.CustomDropComponent; import net.swofty.type.skyblockgeneric.item.components.RegionSelectorComponent; @@ -58,7 +59,14 @@ public void run(PlayerBlockBreakEvent event) { boolean shouldItemDrop = false; // Handle island server block breaks - if (HypixelConst.isIslandServer()) { + if (HypixelConst.isIslandServer() || HypixelConst.isGarden()) { + SkyBlockEditableWorldHandle editableWorld = player.getEditableWorldHandle(); + if (editableWorld == null || !editableWorld.canEdit(event.getBlockPosition())) { + if (editableWorld != null) { + editableWorld.getDeniedBuildMessage(event.getBlockPosition()).ifPresent(player::sendMessage); + } + return; + } event.getInstance().setBlock(event.getBlockPosition(), Block.AIR); shouldItemDrop = true; } @@ -193,4 +201,3 @@ else if (region != null) { } } } - diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockPlace.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockPlace.java index 7eba8c51a..ff792788a 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockPlace.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/event/actions/player/region/ActionRegionBlockPlace.java @@ -7,11 +7,11 @@ import net.swofty.type.generic.event.EventNodes; import net.swofty.type.generic.event.HypixelEvent; import net.swofty.type.generic.event.HypixelEventClass; -import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; import net.swofty.type.generic.utility.MathUtility; +import net.swofty.type.skyblockgeneric.garden.SkyBlockEditableWorldHandle; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; public class ActionRegionBlockPlace implements HypixelEventClass { - private static final int ISLAND_SIZE = 161; private static final Tag PLAYER_PLACED_TAG = Tag.Boolean("player_placed"); @HypixelEvent(node = EventNodes.PLAYER, requireDataLoaded = false) @@ -22,20 +22,20 @@ public void run(PlayerBlockPlaceEvent event) { return; } - if (!HypixelConst.isIslandServer()) { + if (!HypixelConst.isIslandServer() && !HypixelConst.isGarden()) { event.setCancelled(true); return; } - int islandSizePlus = (int) Math.floor((double) ISLAND_SIZE/2); - int islandSizeMinus = -islandSizePlus; Point position = event.getBlockPosition(); - int x = position.blockX(); - int z = position.blockZ(); - - if (x > islandSizePlus || x < islandSizeMinus || z > islandSizePlus || z < islandSizeMinus) { + SkyBlockEditableWorldHandle editableWorld = player.getEditableWorldHandle(); + if (editableWorld == null || !editableWorld.canEdit(position)) { event.setCancelled(true); - player.sendMessage("§cYou can't build any further in this direction!"); + if (editableWorld == null) { + player.sendMessage("§cYou can't build here right now!"); + } else { + editableWorld.getDeniedBuildMessage(position).ifPresent(player::sendMessage); + } return; } @@ -47,4 +47,3 @@ public void run(PlayerBlockPlaceEvent event) { event.setBlock(event.getBlock().withTag(PLAYER_PLACED_TAG, true)); } } - diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/Furniture.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/Furniture.java index dde7654e3..b2678efbb 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/Furniture.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/Furniture.java @@ -4,10 +4,12 @@ import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.LivingEntity; import net.minestom.server.entity.PlayerSkin; import net.minestom.server.entity.metadata.display.BlockDisplayMeta; import net.minestom.server.entity.metadata.display.ItemDisplayMeta; +import net.minestom.server.entity.metadata.other.ArmorStandMeta; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.item.ItemStack; @@ -36,15 +38,18 @@ public static List load(String furnitureName) { } public static List load(String furnitureName, Pos offset) { + return load(HypixelConst.getInstanceContainer(), furnitureName, offset); + } + + public static List load(final Instance instance, final String furnitureName, final Pos offset) { try { - final Instance instance = HypixelConst.getInstanceContainer(); if (instance == null) { - throw new IllegalStateException("SkyBlock instance is not initialized"); + throw new IllegalStateException("Instance is not initialized"); } final File file = new File(FURNITURE_DIR, furnitureName.toLowerCase() + ".json"); if (!file.exists()) { - throw new IllegalArgumentException("Furniture file not found: " + file.getPath()); + throw new FurnitureNotPresent("Furniture file not found: " + file.getPath()); } final String content = Files.readString(file.toPath(), StandardCharsets.UTF_8); @@ -67,12 +72,19 @@ public static List load(String furnitureName, Pos offset) { LivingEntity entity = createBlockDisplay(entry); spawnEntity(entity, entry, offset, instance); spawned.add(entity); + continue; + } + + if ("minecraft:armor_stand".equals(type)) { + LivingEntity entity = createArmorStand(entry); + spawnEntity(entity, entry, offset, instance); + spawned.add(entity); } } return spawned; } catch (Exception exception) { - throw new IllegalStateException("Failed to load furniture '" + furnitureName + "'", exception); + throw new FurnitureLoadException("Failed to load furniture '" + furnitureName + "'", exception); } } @@ -175,6 +187,40 @@ private static LivingEntity createBlockDisplay(final JSONObject entry) { return entity; } + private static LivingEntity createArmorStand(final JSONObject entry) { + final LivingEntity entity = new LivingEntity(EntityType.ARMOR_STAND); + entity.editEntityMeta(ArmorStandMeta.class, meta -> { + meta.setHasNoGravity(true); + meta.setInvisible(entry.optBoolean("invisible", false)); + meta.setSmall(entry.optBoolean("small", false)); + meta.setMarker(entry.optBoolean("marker", false)); + meta.setHasArms(entry.optBoolean("showArms", false)); + meta.setHasNoBasePlate(!entry.optBoolean("showBasePlate", true)); + + JSONObject pose = entry.optJSONObject("pose"); + if (pose != null) { + meta.setHeadRotation(readRotationVector(pose, "head")); + meta.setBodyRotation(readRotationVector(pose, "body")); + meta.setLeftArmRotation(readRotationVector(pose, "leftArm")); + meta.setRightArmRotation(readRotationVector(pose, "rightArm")); + meta.setLeftLegRotation(readRotationVector(pose, "leftLeg")); + meta.setRightLegRotation(readRotationVector(pose, "rightLeg")); + } + }); + + JSONObject equipment = entry.optJSONObject("equipment"); + if (equipment != null) { + setEquipment(entity, equipment, "head", EquipmentSlot.HELMET); + setEquipment(entity, equipment, "chest", EquipmentSlot.CHESTPLATE); + setEquipment(entity, equipment, "legs", EquipmentSlot.LEGGINGS); + setEquipment(entity, equipment, "feet", EquipmentSlot.BOOTS); + setEquipment(entity, equipment, "mainhand", EquipmentSlot.MAIN_HAND); + setEquipment(entity, equipment, "offhand", EquipmentSlot.OFF_HAND); + } + + return entity; + } + private static ItemStack buildItemStack(final JSONObject item) { final String itemId = item.getString("id"); Material material = Material.fromKey(itemId); @@ -220,4 +266,24 @@ private static String extractTextureFromSnbt(final String snbt) { return null; } + private static Vec readRotationVector(JSONObject pose, String key) { + JSONObject rotation = pose.optJSONObject(key); + if (rotation == null) { + return Vec.ZERO; + } + return new Vec( + rotation.optDouble("x", 0D), + rotation.optDouble("y", 0D), + rotation.optDouble("z", 0D) + ); + } + + private static void setEquipment(LivingEntity entity, JSONObject equipment, String key, EquipmentSlot slot) { + JSONObject item = equipment.optJSONObject(key); + if (item == null) { + return; + } + entity.setEquipment(slot, buildItemStack(item)); + } + } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureLoadException.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureLoadException.java new file mode 100644 index 000000000..ecb5120c4 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureLoadException.java @@ -0,0 +1,11 @@ +package net.swofty.type.skyblockgeneric.furniture; + +public class FurnitureLoadException extends RuntimeException { + public FurnitureLoadException(String message) { + super(message); + } + + public FurnitureLoadException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureNotPresent.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureNotPresent.java new file mode 100644 index 000000000..bf06c29e0 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/furniture/FurnitureNotPresent.java @@ -0,0 +1,7 @@ +package net.swofty.type.skyblockgeneric.furniture; + +public class FurnitureNotPresent extends RuntimeException { + public FurnitureNotPresent(String message) { + super(message); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/GardenData.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/GardenData.java new file mode 100644 index 000000000..08656ae33 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/GardenData.java @@ -0,0 +1,1006 @@ +package net.swofty.type.skyblockgeneric.garden; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class GardenData { + private GardenData() { + } + + public static class GardenCoreData { + private long experience = 0; + private int level = 1; + private Set unlockedPlots = new LinkedHashSet<>(Set.of("central")); + private Map cleanedPlots = new HashMap<>(); + private Map plotPresets = new HashMap<>(); + private Map cropUpgrades = new HashMap<>(); + private String selectedBarnSkin = "default"; + private Set ownedBarnSkins = new LinkedHashSet<>(Set.of("default")); + private long copper = 0; + private Set servedUniqueVisitors = new LinkedHashSet<>(); + private Map gardenMilestones = new HashMap<>(); + private Map cropMilestones = new HashMap<>(); + private Map cropMilestoneProgress = new HashMap<>(); + private Map visitorMilestones = new HashMap<>(); + private Map visitorMilestoneProgress = new HashMap<>(); + private Set skyMartPurchases = new LinkedHashSet<>(); + private Set tutorialFlags = new LinkedHashSet<>(); + private Set visitorRequirementFlags = new LinkedHashSet<>(); + private Map visitorRequirementCounters = new HashMap<>(); + private String selectedTimeMode = "DYNAMIC"; + private long lastActiveAt = 0; + + public long getExperience() { + return experience; + } + + public void setExperience(long experience) { + this.experience = experience; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public Set getUnlockedPlots() { + return unlockedPlots; + } + + public void setUnlockedPlots(Set unlockedPlots) { + this.unlockedPlots = unlockedPlots; + } + + public Map getCleanedPlots() { + return cleanedPlots; + } + + public void setCleanedPlots(Map cleanedPlots) { + this.cleanedPlots = cleanedPlots; + } + + public Map getPlotPresets() { + return plotPresets; + } + + public void setPlotPresets(Map plotPresets) { + this.plotPresets = plotPresets; + } + + public Map getCropUpgrades() { + return cropUpgrades; + } + + public void setCropUpgrades(Map cropUpgrades) { + this.cropUpgrades = cropUpgrades; + } + + public String getSelectedBarnSkin() { + return selectedBarnSkin; + } + + public void setSelectedBarnSkin(String selectedBarnSkin) { + this.selectedBarnSkin = selectedBarnSkin; + } + + public Set getOwnedBarnSkins() { + return ownedBarnSkins; + } + + public void setOwnedBarnSkins(Set ownedBarnSkins) { + this.ownedBarnSkins = ownedBarnSkins; + } + + public long getCopper() { + return copper; + } + + public void setCopper(long copper) { + this.copper = copper; + } + + public Set getServedUniqueVisitors() { + return servedUniqueVisitors; + } + + public void setServedUniqueVisitors(Set servedUniqueVisitors) { + this.servedUniqueVisitors = servedUniqueVisitors; + } + + public Map getGardenMilestones() { + return gardenMilestones; + } + + public void setGardenMilestones(Map gardenMilestones) { + this.gardenMilestones = gardenMilestones; + } + + public Map getCropMilestones() { + return cropMilestones; + } + + public void setCropMilestones(Map cropMilestones) { + this.cropMilestones = cropMilestones; + } + + public Map getCropMilestoneProgress() { + return cropMilestoneProgress; + } + + public void setCropMilestoneProgress(Map cropMilestoneProgress) { + this.cropMilestoneProgress = cropMilestoneProgress; + } + + public Map getVisitorMilestones() { + return visitorMilestones; + } + + public void setVisitorMilestones(Map visitorMilestones) { + this.visitorMilestones = visitorMilestones; + } + + public Map getVisitorMilestoneProgress() { + return visitorMilestoneProgress; + } + + public void setVisitorMilestoneProgress(Map visitorMilestoneProgress) { + this.visitorMilestoneProgress = visitorMilestoneProgress; + } + + public Set getSkyMartPurchases() { + return skyMartPurchases; + } + + public void setSkyMartPurchases(Set skyMartPurchases) { + this.skyMartPurchases = skyMartPurchases; + } + + public Set getTutorialFlags() { + return tutorialFlags; + } + + public void setTutorialFlags(Set tutorialFlags) { + this.tutorialFlags = tutorialFlags; + } + + public Set getVisitorRequirementFlags() { + return visitorRequirementFlags; + } + + public void setVisitorRequirementFlags(Set visitorRequirementFlags) { + this.visitorRequirementFlags = visitorRequirementFlags; + } + + public Map getVisitorRequirementCounters() { + return visitorRequirementCounters; + } + + public void setVisitorRequirementCounters(Map visitorRequirementCounters) { + this.visitorRequirementCounters = visitorRequirementCounters; + } + + public String getSelectedTimeMode() { + return selectedTimeMode; + } + + public void setSelectedTimeMode(String selectedTimeMode) { + this.selectedTimeMode = selectedTimeMode; + } + + public long getLastActiveAt() { + return lastActiveAt; + } + + public void setLastActiveAt(long lastActiveAt) { + this.lastActiveAt = lastActiveAt; + } + } + + public static class GardenVisitorsData { + private List activeVisitors = new ArrayList<>(); + private List queuedVisitors = new ArrayList<>(); + private Map visitCounts = new HashMap<>(); + private Map servedCounts = new HashMap<>(); + private Set logbookEntries = new LinkedHashSet<>(); + private long nextArrivalAt = 0; + private long lastProcessedAt = 0; + private long lastFarmingActivityAt = 0; + + public List getActiveVisitors() { + return activeVisitors; + } + + public void setActiveVisitors(List activeVisitors) { + this.activeVisitors = activeVisitors; + } + + public List getQueuedVisitors() { + return queuedVisitors; + } + + public void setQueuedVisitors(List queuedVisitors) { + this.queuedVisitors = queuedVisitors; + } + + public Map getServedCounts() { + return servedCounts; + } + + public void setServedCounts(Map servedCounts) { + this.servedCounts = servedCounts; + } + + public Map getVisitCounts() { + return visitCounts; + } + + public void setVisitCounts(Map visitCounts) { + this.visitCounts = visitCounts; + } + + public Set getLogbookEntries() { + return logbookEntries; + } + + public void setLogbookEntries(Set logbookEntries) { + this.logbookEntries = logbookEntries; + } + + public long getNextArrivalAt() { + return nextArrivalAt; + } + + public void setNextArrivalAt(long nextArrivalAt) { + this.nextArrivalAt = nextArrivalAt; + } + + public long getLastProcessedAt() { + return lastProcessedAt; + } + + public void setLastProcessedAt(long lastProcessedAt) { + this.lastProcessedAt = lastProcessedAt; + } + + public long getLastFarmingActivityAt() { + return lastFarmingActivityAt; + } + + public void setLastFarmingActivityAt(long lastFarmingActivityAt) { + this.lastFarmingActivityAt = lastFarmingActivityAt; + } + } + + public static class GardenVisitorState { + private String visitorId = ""; + private String rarity = "UNCOMMON"; + private List requests = new ArrayList<>(); + private long farmingXp = 0; + private int gardenXp = 0; + private int copper = 0; + private int bits = 0; + private List guaranteedRewards = new ArrayList<>(); + private List bonusRewards = new ArrayList<>(); + private long arrivedAt = 0; + private boolean queued = false; + + public String getVisitorId() { + return visitorId; + } + + public void setVisitorId(String visitorId) { + this.visitorId = visitorId; + } + + public String getRarity() { + return rarity; + } + + public void setRarity(String rarity) { + this.rarity = rarity; + } + + public List getRequests() { + return requests; + } + + public void setRequests(List requests) { + this.requests = requests; + } + + public long getFarmingXp() { + return farmingXp; + } + + public void setFarmingXp(long farmingXp) { + this.farmingXp = farmingXp; + } + + public int getGardenXp() { + return gardenXp; + } + + public void setGardenXp(int gardenXp) { + this.gardenXp = gardenXp; + } + + public int getCopper() { + return copper; + } + + public void setCopper(int copper) { + this.copper = copper; + } + + public int getBits() { + return bits; + } + + public void setBits(int bits) { + this.bits = bits; + } + + public List getGuaranteedRewards() { + return normalizeRewards(guaranteedRewards); + } + + public void setGuaranteedRewards(List guaranteedRewards) { + this.guaranteedRewards = guaranteedRewards == null ? new ArrayList<>() : new ArrayList<>(guaranteedRewards); + } + + public List getBonusRewards() { + return normalizeRewards(bonusRewards); + } + + public void setBonusRewards(List bonusRewards) { + this.bonusRewards = bonusRewards == null ? new ArrayList<>() : new ArrayList<>(bonusRewards); + } + + public long getArrivedAt() { + return arrivedAt; + } + + public void setArrivedAt(long arrivedAt) { + this.arrivedAt = arrivedAt; + } + + public boolean isQueued() { + return queued; + } + + public void setQueued(boolean queued) { + this.queued = queued; + } + + private static List normalizeRewards(List rawRewards) { + List normalized = new ArrayList<>(); + if (rawRewards == null) { + return normalized; + } + for (Object rawReward : rawRewards) { + if (rawReward instanceof GardenRewardState rewardState) { + normalized.add(rewardState); + continue; + } + if (rawReward instanceof Map rewardMap) { + GardenRewardState rewardState = new GardenRewardState(); + rewardState.setType(String.valueOf(rewardMap.containsKey("type") ? rewardMap.get("type") : "PROFILE_FLAG")); + rewardState.setKey(String.valueOf(rewardMap.containsKey("key") ? rewardMap.get("key") : "")); + rewardState.setAmount(parseLong(rewardMap.get("amount"), 0L)); + rewardState.setMin(parseLong(rewardMap.get("min"), 0L)); + rewardState.setMax(parseLong(rewardMap.get("max"), 0L)); + rewardState.setPoolId(String.valueOf(rewardMap.containsKey("poolId") ? rewardMap.get("poolId") : "")); + rewardState.setFirstVisitOnly(Boolean.parseBoolean(String.valueOf( + rewardMap.containsKey("firstVisitOnly") ? rewardMap.get("firstVisitOnly") : false + ))); + rewardState.setDisplayOverride(String.valueOf( + rewardMap.containsKey("displayOverride") ? rewardMap.get("displayOverride") : "" + )); + Object metadata = rewardMap.get("metadata"); + if (metadata instanceof Map metadataMap) { + Map normalizedMetadata = new LinkedHashMap<>(); + metadataMap.forEach((metadataKey, metadataValue) -> normalizedMetadata.put( + String.valueOf(metadataKey), + String.valueOf(metadataValue) + )); + rewardState.setMetadata(normalizedMetadata); + } + normalized.add(rewardState); + continue; + } + + GardenRewardState rewardState = new GardenRewardState(); + rewardState.setType("LEGACY"); + rewardState.setKey(String.valueOf(rawReward)); + normalized.add(rewardState); + } + return normalized; + } + + private static long parseLong(Object rawValue, long defaultValue) { + if (rawValue instanceof Number number) { + return number.longValue(); + } + if (rawValue == null) { + return defaultValue; + } + try { + return Long.parseLong(String.valueOf(rawValue)); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + } + + public static class GardenRequest { + private String itemId = ""; + private int amount = 0; + private double itemQuantityMultiplier = 1; + + public String getItemId() { + return itemId; + } + + public void setItemId(String itemId) { + this.itemId = itemId; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public double getItemQuantityMultiplier() { + return itemQuantityMultiplier; + } + + public void setItemQuantityMultiplier(double itemQuantityMultiplier) { + this.itemQuantityMultiplier = itemQuantityMultiplier; + } + } + + public static class GardenRewardState { + private String type = ""; + private String key = ""; + private long amount = 0; + private long min = 0; + private long max = 0; + private String poolId = ""; + private boolean firstVisitOnly = false; + private String displayOverride = ""; + private Map metadata = new LinkedHashMap<>(); + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public long getAmount() { + return amount; + } + + public void setAmount(long amount) { + this.amount = amount; + } + + public long getMin() { + return min; + } + + public void setMin(long min) { + this.min = min; + } + + public long getMax() { + return max; + } + + public void setMax(long max) { + this.max = max; + } + + public String getPoolId() { + return poolId; + } + + public void setPoolId(String poolId) { + this.poolId = poolId; + } + + public boolean isFirstVisitOnly() { + return firstVisitOnly; + } + + public void setFirstVisitOnly(boolean firstVisitOnly) { + this.firstVisitOnly = firstVisitOnly; + } + + public String getDisplayOverride() { + return displayOverride; + } + + public void setDisplayOverride(String displayOverride) { + this.displayOverride = displayOverride; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + } + + public static class GardenPestsData { + private List activePests = new ArrayList<>(); + private int storedPests = 0; + private long cooldownEndsAt = 0; + private long lastSpawnCheckAt = 0; + private long offlineAccumulatorMs = 0; + private long repellentEndsAt = 0; + private boolean repellentMax = false; + + public List getActivePests() { + return activePests; + } + + public void setActivePests(List activePests) { + this.activePests = activePests; + } + + public int getStoredPests() { + return storedPests; + } + + public void setStoredPests(int storedPests) { + this.storedPests = storedPests; + } + + public long getCooldownEndsAt() { + return cooldownEndsAt; + } + + public void setCooldownEndsAt(long cooldownEndsAt) { + this.cooldownEndsAt = cooldownEndsAt; + } + + public long getLastSpawnCheckAt() { + return lastSpawnCheckAt; + } + + public void setLastSpawnCheckAt(long lastSpawnCheckAt) { + this.lastSpawnCheckAt = lastSpawnCheckAt; + } + + public long getOfflineAccumulatorMs() { + return offlineAccumulatorMs; + } + + public void setOfflineAccumulatorMs(long offlineAccumulatorMs) { + this.offlineAccumulatorMs = offlineAccumulatorMs; + } + + public long getRepellentEndsAt() { + return repellentEndsAt; + } + + public void setRepellentEndsAt(long repellentEndsAt) { + this.repellentEndsAt = repellentEndsAt; + } + + public boolean isRepellentMax() { + return repellentMax; + } + + public void setRepellentMax(boolean repellentMax) { + this.repellentMax = repellentMax; + } + } + + public static class GardenPestState { + private String pestId = ""; + private String plotId = ""; + private long spawnedAt = 0; + private boolean offlineSpawned = false; + private boolean trapSpawned = false; + + public String getPestId() { + return pestId; + } + + public void setPestId(String pestId) { + this.pestId = pestId; + } + + public String getPlotId() { + return plotId; + } + + public void setPlotId(String plotId) { + this.plotId = plotId; + } + + public long getSpawnedAt() { + return spawnedAt; + } + + public void setSpawnedAt(long spawnedAt) { + this.spawnedAt = spawnedAt; + } + + public boolean isOfflineSpawned() { + return offlineSpawned; + } + + public void setOfflineSpawned(boolean offlineSpawned) { + this.offlineSpawned = offlineSpawned; + } + + public boolean isTrapSpawned() { + return trapSpawned; + } + + public void setTrapSpawned(boolean trapSpawned) { + this.trapSpawned = trapSpawned; + } + } + + public static class GardenComposterData { + private double organicMatter = 0; + private double fuel = 0; + private int compostAvailable = 0; + private long lastUpdatedAt = 0; + private Map upgrades = new HashMap<>(); + private Map insertedMatter = new HashMap<>(); + private Map insertedFuel = new HashMap<>(); + + public double getOrganicMatter() { + return organicMatter; + } + + public void setOrganicMatter(double organicMatter) { + this.organicMatter = organicMatter; + } + + public double getFuel() { + return fuel; + } + + public void setFuel(double fuel) { + this.fuel = fuel; + } + + public int getCompostAvailable() { + return compostAvailable; + } + + public void setCompostAvailable(int compostAvailable) { + this.compostAvailable = compostAvailable; + } + + public long getLastUpdatedAt() { + return lastUpdatedAt; + } + + public void setLastUpdatedAt(long lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + + public Map getUpgrades() { + return upgrades; + } + + public void setUpgrades(Map upgrades) { + this.upgrades = upgrades; + } + + public Map getInsertedMatter() { + return insertedMatter; + } + + public void setInsertedMatter(Map insertedMatter) { + this.insertedMatter = insertedMatter; + } + + public Map getInsertedFuel() { + return insertedFuel; + } + + public void setInsertedFuel(Map insertedFuel) { + this.insertedFuel = insertedFuel; + } + } + + public static class GardenGreenhouseData { + private boolean blueprintUnlocked = false; + private boolean greenhouseUnlocked = false; + private int unlockedGreenhouses = 0; + private int unlockedPlots = 0; + private int spentEtherealVines = 0; + private Set unlockedSlots = new LinkedHashSet<>(); + private Map upgrades = new HashMap<>(); + private Set analyzedMutations = new LinkedHashSet<>(); + private Map mutationHarvests = new HashMap<>(); + private int dnaMilestone = 0; + private Set ownedGreenhouseSkins = new LinkedHashSet<>(Set.of("default")); + private String selectedGreenhouseSkin = "default"; + + public boolean isBlueprintUnlocked() { + return blueprintUnlocked; + } + + public void setBlueprintUnlocked(boolean blueprintUnlocked) { + this.blueprintUnlocked = blueprintUnlocked; + } + + public boolean isGreenhouseUnlocked() { + return greenhouseUnlocked; + } + + public void setGreenhouseUnlocked(boolean greenhouseUnlocked) { + this.greenhouseUnlocked = greenhouseUnlocked; + } + + public int getUnlockedGreenhouses() { + return unlockedGreenhouses; + } + + public void setUnlockedGreenhouses(int unlockedGreenhouses) { + this.unlockedGreenhouses = unlockedGreenhouses; + } + + public int getUnlockedPlots() { + return unlockedPlots; + } + + public void setUnlockedPlots(int unlockedPlots) { + this.unlockedPlots = unlockedPlots; + } + + public int getSpentEtherealVines() { + return spentEtherealVines; + } + + public void setSpentEtherealVines(int spentEtherealVines) { + this.spentEtherealVines = spentEtherealVines; + } + + public Set getUnlockedSlots() { + return unlockedSlots; + } + + public void setUnlockedSlots(Set unlockedSlots) { + this.unlockedSlots = unlockedSlots; + } + + public Map getUpgrades() { + return upgrades; + } + + public void setUpgrades(Map upgrades) { + this.upgrades = upgrades; + } + + public Set getAnalyzedMutations() { + return analyzedMutations; + } + + public void setAnalyzedMutations(Set analyzedMutations) { + this.analyzedMutations = analyzedMutations; + } + + public Map getMutationHarvests() { + return mutationHarvests; + } + + public void setMutationHarvests(Map mutationHarvests) { + this.mutationHarvests = mutationHarvests; + } + + public int getDnaMilestone() { + return dnaMilestone; + } + + public void setDnaMilestone(int dnaMilestone) { + this.dnaMilestone = dnaMilestone; + } + + public Set getOwnedGreenhouseSkins() { + return ownedGreenhouseSkins; + } + + public void setOwnedGreenhouseSkins(Set ownedGreenhouseSkins) { + this.ownedGreenhouseSkins = ownedGreenhouseSkins; + } + + public String getSelectedGreenhouseSkin() { + return selectedGreenhouseSkin; + } + + public void setSelectedGreenhouseSkin(String selectedGreenhouseSkin) { + this.selectedGreenhouseSkin = selectedGreenhouseSkin; + } + } + + public static class GardenPersonalData { + private double sowdust = 0; + private int jacobsTickets = 0; + private Map medals = new HashMap<>(); + private Map chips = new HashMap<>(); + private Map contestPersonalBests = new HashMap<>(); + private Set claimedContestRewards = new LinkedHashSet<>(); + private Set spokenNpcFlags = new LinkedHashSet<>(); + private Set anitaPurchases = new LinkedHashSet<>(); + private Set tutorialFlags = new LinkedHashSet<>(); + private Set visitorRequirementFlags = new LinkedHashSet<>(); + private Map visitorRequirementCounters = new HashMap<>(); + private Set donatedItems = new LinkedHashSet<>(); + private Map exportedItems = new HashMap<>(); + private Set accessFlags = new LinkedHashSet<>(); + + public double getSowdust() { + return sowdust; + } + + public void setSowdust(double sowdust) { + this.sowdust = sowdust; + } + + public int getJacobsTickets() { + return jacobsTickets; + } + + public void setJacobsTickets(int jacobsTickets) { + this.jacobsTickets = jacobsTickets; + } + + public Map getMedals() { + return medals; + } + + public void setMedals(Map medals) { + this.medals = medals; + } + + public Map getChips() { + return chips; + } + + public void setChips(Map chips) { + this.chips = chips; + } + + public Map getContestPersonalBests() { + return contestPersonalBests; + } + + public void setContestPersonalBests(Map contestPersonalBests) { + this.contestPersonalBests = contestPersonalBests; + } + + public Set getClaimedContestRewards() { + return claimedContestRewards; + } + + public void setClaimedContestRewards(Set claimedContestRewards) { + this.claimedContestRewards = claimedContestRewards; + } + + public Set getSpokenNpcFlags() { + return spokenNpcFlags; + } + + public void setSpokenNpcFlags(Set spokenNpcFlags) { + this.spokenNpcFlags = spokenNpcFlags; + } + + public Set getAnitaPurchases() { + return anitaPurchases; + } + + public void setAnitaPurchases(Set anitaPurchases) { + this.anitaPurchases = anitaPurchases; + } + + public Set getTutorialFlags() { + return tutorialFlags; + } + + public void setTutorialFlags(Set tutorialFlags) { + this.tutorialFlags = tutorialFlags; + } + + public Set getVisitorRequirementFlags() { + return visitorRequirementFlags; + } + + public void setVisitorRequirementFlags(Set visitorRequirementFlags) { + this.visitorRequirementFlags = visitorRequirementFlags; + } + + public Map getVisitorRequirementCounters() { + return visitorRequirementCounters; + } + + public void setVisitorRequirementCounters(Map visitorRequirementCounters) { + this.visitorRequirementCounters = visitorRequirementCounters; + } + + public Set getDonatedItems() { + return donatedItems; + } + + public void setDonatedItems(Set donatedItems) { + this.donatedItems = donatedItems; + } + + public Map getExportedItems() { + return exportedItems; + } + + public void setExportedItems(Map exportedItems) { + this.exportedItems = exportedItems; + } + + public Set getAccessFlags() { + return accessFlags; + } + + public void setAccessFlags(Set accessFlags) { + this.accessFlags = accessFlags; + } + } + + public static class GardenChipProgress { + private int consumed = 0; + private String rarity = "LOCKED"; + private int level = 0; + + public int getConsumed() { + return consumed; + } + + public void setConsumed(int consumed) { + this.consumed = consumed; + } + + public String getRarity() { + return rarity; + } + + public void setRarity(String rarity) { + this.rarity = rarity; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockEditableWorldHandle.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockEditableWorldHandle.java new file mode 100644 index 000000000..a6758d42f --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockEditableWorldHandle.java @@ -0,0 +1,24 @@ +package net.swofty.type.skyblockgeneric.garden; + +import net.minestom.server.coordinate.Point; + +import java.util.Optional; + +public interface SkyBlockEditableWorldHandle { + WorldBuildLimits getBuildLimits(); + + default boolean isWithinBounds(Point point) { + return getBuildLimits().contains(point); + } + + default boolean canEdit(Point point) { + return isWithinBounds(point); + } + + default Optional getDeniedBuildMessage(Point point) { + if (!isWithinBounds(point)) { + return Optional.of("§cYou can't build any further in this direction!"); + } + return Optional.of("§cYou can't edit that part yet!"); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockGardenHandle.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockGardenHandle.java new file mode 100644 index 000000000..b29a89fda --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/SkyBlockGardenHandle.java @@ -0,0 +1,4 @@ +package net.swofty.type.skyblockgeneric.garden; + +public interface SkyBlockGardenHandle extends SkyBlockEditableWorldHandle { +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/WorldBuildLimits.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/WorldBuildLimits.java new file mode 100644 index 000000000..06cad1497 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/WorldBuildLimits.java @@ -0,0 +1,11 @@ +package net.swofty.type.skyblockgeneric.garden; + +import net.minestom.server.coordinate.Point; + +public record WorldBuildLimits(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; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionReward.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionReward.java new file mode 100644 index 000000000..60b6bbd0a --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionReward.java @@ -0,0 +1,23 @@ +package net.swofty.type.skyblockgeneric.garden.progression; + +public record GardenProgressionReward(String type, String key, long amount) { + public static GardenProgressionReward spokenNpc(String key) { + return new GardenProgressionReward("SPOKEN_TO_NPC", key, 1L); + } + + public static GardenProgressionReward flag(String key) { + return new GardenProgressionReward("PROFILE_FLAG", key, 1L); + } + + public static GardenProgressionReward donatedItem(String key) { + return new GardenProgressionReward("ITEM_DONATED", key, 1L); + } + + public static GardenProgressionReward exportedItem(String key, long amount) { + return new GardenProgressionReward("ITEM_EXPORTED", key, amount); + } + + public static GardenProgressionReward counter(String key, long amount) { + return new GardenProgressionReward("PROFILE_COUNTER", key, amount); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSource.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSource.java new file mode 100644 index 000000000..1d5583bd1 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSource.java @@ -0,0 +1,11 @@ +package net.swofty.type.skyblockgeneric.garden.progression; + +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; + +public interface GardenProgressionSource { + default List gardenProgressionRewards(SkyBlockPlayer player) { + return List.of(); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSupport.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSupport.java new file mode 100644 index 000000000..acb9bebd1 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenProgressionSupport.java @@ -0,0 +1,64 @@ +package net.swofty.type.skyblockgeneric.garden.progression; + +import net.swofty.type.skyblockgeneric.data.SkyBlockDataHandler; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenCore; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenPersonal; +import net.swofty.type.skyblockgeneric.garden.GardenData; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.Locale; + +public final class GardenProgressionSupport { + private GardenProgressionSupport() { + } + + public static void apply(SkyBlockPlayer player, GardenProgressionReward reward) { + if (player == null || reward == null) { + return; + } + GardenData.GardenPersonalData personal = player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_PERSONAL, DatapointGardenPersonal.class) + .getValue(); + GardenData.GardenCoreData core = player.getSkyblockDataHandler() + .get(SkyBlockDataHandler.Data.GARDEN_CORE, DatapointGardenCore.class) + .getValue(); + + switch (reward.type()) { + case "SPOKEN_TO_NPC" -> personal.getSpokenNpcFlags().add(normalizeSpokenKey(reward.key())); + case "PROFILE_FLAG" -> personal.getVisitorRequirementFlags().add(normalizeKey(reward.key())); + case "ITEM_DONATED" -> personal.getDonatedItems().add(normalizeKey(reward.key())); + case "ITEM_EXPORTED" -> + personal.getExportedItems().merge(normalizeKey(reward.key()), Math.max(0L, reward.amount()), Long::sum); + case "PROFILE_COUNTER" -> + personal.getVisitorRequirementCounters().merge(normalizeKey(reward.key()), reward.amount(), Long::sum); + case "CORE_FLAG" -> core.getVisitorRequirementFlags().add(normalizeKey(reward.key())); + case "CORE_COUNTER" -> + core.getVisitorRequirementCounters().merge(normalizeKey(reward.key()), reward.amount(), Long::sum); + default -> { + } + } + } + + public static void apply(SkyBlockPlayer player, GardenProgressionReward... rewards) { + if (rewards == null) { + return; + } + for (GardenProgressionReward reward : rewards) { + apply(player, reward); + } + } + + public static String normalizeKey(String key) { + if (key == null) { + return ""; + } + return key.trim() + .replaceAll("[^A-Za-z0-9]+", "_") + .replaceAll("^_+|_+$", "") + .toUpperCase(Locale.ROOT); + } + + public static String normalizeSpokenKey(String key) { + return normalizeKey(key).toLowerCase(Locale.ROOT); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenSpokenNpcSource.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenSpokenNpcSource.java new file mode 100644 index 000000000..6b5518175 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/garden/progression/GardenSpokenNpcSource.java @@ -0,0 +1,14 @@ +package net.swofty.type.skyblockgeneric.garden.progression; + +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.List; + +public interface GardenSpokenNpcSource extends GardenProgressionSource { + String gardenSpokenNpcId(); + + @Override + default List gardenProgressionRewards(SkyBlockPlayer player) { + return List.of(GardenProgressionReward.spokenNpc(gardenSpokenNpcId())); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/ability/abilities/BuildersWandAbility.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/ability/abilities/BuildersWandAbility.java index a59fce0b9..a433e9bb6 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/ability/abilities/BuildersWandAbility.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/ability/abilities/BuildersWandAbility.java @@ -65,7 +65,7 @@ private static boolean fillConnectedFaces(SkyBlockPlayer player, Pos origin, Blo } } Pos fillBlock = l.add(translate); - if (HypixelConst.isIslandServer()) { + if (HypixelConst.isIslandServer() || HypixelConst.isGarden()) { blocks.removeIf(blocks.getFirst()::equals); if (!player.getInstance().getBlock(fillBlock).key().equals(fillMaterial.key())) { player.getInstance().setBlock(fillBlock, Block.fromKey(fillMaterial.key())); diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/interactable/InteractableRegistry.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/interactable/InteractableRegistry.java index 55f273779..547ede5cb 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/interactable/InteractableRegistry.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/interactable/InteractableRegistry.java @@ -27,7 +27,7 @@ public class InteractableRegistry { ).build()); register("MOVE_JERRY_INTERACT", InteractableItemConfig.builder().rightClickHandler( ((player, skyBlockItem) -> { - if (!HypixelConst.isIslandServer()) { + if (!HypixelConst.isIslandServer() && !HypixelConst.isGarden()) { player.sendMessage("§cYou can't move Jerry here! He doesn't belong here!"); return; } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/place/PlaceEventRegistry.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/place/PlaceEventRegistry.java index 03c644132..cdca58f3f 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/place/PlaceEventRegistry.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/item/handlers/place/PlaceEventRegistry.java @@ -1,15 +1,12 @@ package net.swofty.type.skyblockgeneric.item.handlers.place; import net.minestom.server.coordinate.Pos; -import net.minestom.server.event.player.PlayerBlockPlaceEvent; import net.minestom.server.instance.block.Block; import net.swofty.type.generic.HypixelConst; import net.swofty.type.skyblockgeneric.data.SkyBlockDataHandler; import net.swofty.type.skyblockgeneric.data.datapoints.DatapointMinionData; -import net.swofty.type.skyblockgeneric.item.SkyBlockItem; import net.swofty.type.skyblockgeneric.item.components.MinionComponent; import net.swofty.type.skyblockgeneric.minion.IslandMinionData; -import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; import java.util.HashMap; import java.util.Map; @@ -19,7 +16,7 @@ public class PlaceEventRegistry { static { register("minion", (event, player, item) -> { - if (!HypixelConst.isIslandServer()) { + if (!HypixelConst.isIslandServer() && !HypixelConst.isGarden()) { player.sendMessage("§cYou can only place minions on your island!"); event.setCancelled(true); return; diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/RegionType.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/RegionType.java index 9c3fb7d69..082cb0f1b 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/RegionType.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/RegionType.java @@ -32,6 +32,7 @@ @Getter public enum RegionType { PRIVATE_ISLAND("Your Island", "§a"), + THE_GARDEN("The Garden", "§c"), VILLAGE("Village", WheatAndFlowersConfiguration.class), BANK("Bank", "§6"), diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/SkyBlockRegion.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/SkyBlockRegion.java index a71b309e3..a213d5e46 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/SkyBlockRegion.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/region/SkyBlockRegion.java @@ -207,7 +207,7 @@ public List getBounds() { public static void cacheRegions() { for (SkyBlockRegion region : RegionDatabase.getAllRegions()) { - if (region.getType() == null || region.getType() == RegionType.PRIVATE_ISLAND) { + if (region.getType() == null || region.getType() == RegionType.PRIVATE_ISLAND || region.getType() == RegionType.THE_GARDEN) { region.delete(); } else { ServerType typeOfRegion = region.getServerType(); @@ -222,9 +222,18 @@ public static void cacheRegions() { new Pos(0, 0, 0), RegionType.PRIVATE_ISLAND, ServerType.SKYBLOCK_ISLAND)); + REGION_CACHE.put("the_garden", new SkyBlockRegion("the_garden", + new Pos(0, 0, 0), + new Pos(0, 0, 0), + RegionType.THE_GARDEN, + ServerType.SKYBLOCK_GARDEN)); } public static SkyBlockRegion getIslandRegion() { return REGION_CACHE.get("island"); } + + public static SkyBlockRegion getGardenRegion() { + return REGION_CACHE.get("the_garden"); + } } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockIsland.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockIsland.java index b952fbf5b..885ba8856 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockIsland.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockIsland.java @@ -2,7 +2,12 @@ import lombok.Getter; import lombok.Setter; -import net.hollowcube.polar.*; +import net.hollowcube.polar.AnvilPolar; +import net.hollowcube.polar.ChunkSelector; +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.instance.InstanceContainer; @@ -15,17 +20,19 @@ import net.minestom.server.world.DimensionType; import net.swofty.commons.CustomWorlds; import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.event.HypixelEventHandler; import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.generic.utility.MathUtility; import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; import net.swofty.type.skyblockgeneric.data.monogdb.CoopDatabase; import net.swofty.type.skyblockgeneric.data.monogdb.IslandDatabase; -import net.swofty.type.generic.event.HypixelEventHandler; import net.swofty.type.skyblockgeneric.event.custom.IslandFetchedFromDatabaseEvent; import net.swofty.type.skyblockgeneric.event.custom.IslandFirstCreatedEvent; import net.swofty.type.skyblockgeneric.event.custom.IslandSavedIntoDatabaseEvent; +import net.swofty.type.skyblockgeneric.garden.SkyBlockEditableWorldHandle; +import net.swofty.type.skyblockgeneric.garden.WorldBuildLimits; import net.swofty.type.skyblockgeneric.minion.IslandMinionData; import net.swofty.type.skyblockgeneric.utility.JerryInformation; -import net.swofty.type.generic.utility.MathUtility; import org.bson.types.Binary; import org.jetbrains.annotations.Nullable; import org.tinylog.Logger; @@ -39,8 +46,9 @@ import java.util.concurrent.CompletableFuture; @Getter -public class SkyBlockIsland { +public class SkyBlockIsland implements SkyBlockEditableWorldHandle { private static final String ISLAND_TEMPLATE_NAME = CustomWorlds.SKYBLOCK_ISLAND_TEMPLATE.getFolderName(); + private static final WorldBuildLimits BUILD_LIMITS = new WorldBuildLimits(-80, 80, -80, 80); private static final Map loadedIslands = new HashMap<>(); // Internal Island Data @@ -197,4 +205,9 @@ public static void runVacantLoop(Scheduler scheduler) { return TaskSchedule.tick(4); }, ExecutionType.TICK_END); } -} \ No newline at end of file + + @Override + public WorldBuildLimits getBuildLimits() { + return BUILD_LIMITS; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockPlayer.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockPlayer.java index 17aca8a03..04e54b3bf 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockPlayer.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockPlayer.java @@ -16,16 +16,22 @@ import net.minestom.server.network.packet.server.play.UpdateHealthPacket; import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.PlayerConnection; +import net.swofty.commons.ServerType; +import net.swofty.commons.StringUtility; import net.swofty.commons.skyblock.PlayerShopData; import net.swofty.commons.skyblock.SkyBlockPlayerProfiles; -import net.swofty.commons.StringUtility; import net.swofty.commons.skyblock.item.ItemType; import net.swofty.commons.skyblock.item.Rarity; import net.swofty.commons.skyblock.item.UnderstandableSkyBlockItem; import net.swofty.commons.skyblock.statistics.ItemStatistic; import net.swofty.type.generic.HypixelConst; import net.swofty.type.generic.data.HypixelDataHandler; -import net.swofty.type.generic.data.datapoints.*; +import net.swofty.type.generic.data.datapoints.DatapointBoolean; +import net.swofty.type.generic.data.datapoints.DatapointDouble; +import net.swofty.type.generic.data.datapoints.DatapointInteger; +import net.swofty.type.generic.data.datapoints.DatapointLong; +import net.swofty.type.generic.data.datapoints.DatapointRank; +import net.swofty.type.generic.data.datapoints.DatapointString; import net.swofty.type.generic.gui.inventory.HypixelInventoryGUI; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.skyblockgeneric.SkyBlockGenericLoader; @@ -39,6 +45,8 @@ import net.swofty.type.skyblockgeneric.event.value.ValueUpdateEvent; import net.swofty.type.skyblockgeneric.event.value.events.MaxHealthValueUpdateEvent; import net.swofty.type.skyblockgeneric.event.value.events.MiningValueUpdateEvent; +import net.swofty.type.skyblockgeneric.garden.SkyBlockEditableWorldHandle; +import net.swofty.type.skyblockgeneric.garden.SkyBlockGardenHandle; import net.swofty.type.skyblockgeneric.item.SkyBlockItem; import net.swofty.type.skyblockgeneric.item.SkyBlockItemComponent; import net.swofty.type.skyblockgeneric.item.components.AccessoryComponent; @@ -61,7 +69,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.regex.Matcher; @@ -89,6 +101,8 @@ public class SkyBlockPlayer extends HypixelPlayer { public boolean speedManaged = false; @Setter private SkyBlockIsland skyBlockIsland; + @Setter + private SkyBlockGardenHandle skyBlockGarden; private static final Pattern SACK_PATTERN = Pattern.compile("^(?:(SMALL|MEDIUM|LARGE|ENCHANTED)_)?(.+?)_SACK$"); @@ -170,11 +184,33 @@ public DatapointSkills.PlayerSkills getSkills() { } public boolean isOnIsland() { + if (skyBlockIsland == null || HypixelConst.getTypeLoader().getType() != ServerType.SKYBLOCK_ISLAND) { + return false; + } + return getInstance() != null + && getInstance() != HypixelConst.getInstanceContainer() + && getInstance() != HypixelConst.getEmptyInstance(); + } + + public boolean isOnGarden() { + if (skyBlockGarden == null || HypixelConst.getTypeLoader().getType() != ServerType.SKYBLOCK_GARDEN) { + return false; + } return getInstance() != null && getInstance() != HypixelConst.getInstanceContainer() && getInstance() != HypixelConst.getEmptyInstance(); } + public @Nullable SkyBlockEditableWorldHandle getEditableWorldHandle() { + if (HypixelConst.getTypeLoader().getType() == ServerType.SKYBLOCK_GARDEN && skyBlockGarden != null) { + return skyBlockGarden; + } + if (HypixelConst.getTypeLoader().getType() == ServerType.SKYBLOCK_ISLAND) { + return skyBlockIsland; + } + return null; + } + public @Nullable SkyBlockItem getArrow() { for (int i = 0; i < 36; i++) { ItemStack stack = getInventory().getItemStack(i); @@ -427,7 +463,10 @@ public SkyBlockItem updateItemInSlot(int slot, Consumer update) { } public @Nullable SkyBlockRegion getRegion() { - if (isOnIsland()) return SkyBlockRegion.getIslandRegion(); + if (HypixelConst.getTypeLoader().getType() == ServerType.SKYBLOCK_GARDEN) { + return SkyBlockRegion.getGardenRegion(); + } + if (getEditableWorldHandle() != null) return SkyBlockRegion.getIslandRegion(); return SkyBlockRegion.getRegionOfPosition(this.getPosition()); } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockScoreboard.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockScoreboard.java index 00728f222..2cc87c199 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockScoreboard.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/user/SkyBlockScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.skyblockgeneric.user; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -17,6 +19,9 @@ import net.swofty.type.skyblockgeneric.calendar.SkyBlockCalendar; import net.swofty.type.skyblockgeneric.darkauction.DarkAuctionHandler; import net.swofty.type.skyblockgeneric.data.SkyBlockDataHandler; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenCore; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointGardenPersonal; +import net.swofty.type.skyblockgeneric.garden.GardenData; import net.swofty.type.skyblockgeneric.item.SkyBlockItem; import net.swofty.type.skyblockgeneric.mission.LocationAssociatedMission; import net.swofty.type.skyblockgeneric.mission.MissionData; @@ -29,6 +34,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Objects; public class SkyBlockScoreboard { private static final HypixelScoreboard scoreboard = new HypixelScoreboard(); @@ -52,77 +58,114 @@ public static void start() { continue; } - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); - lines.add("§f " + SkyBlockCalendar.getMonthName() + " " + StringUtility.ntify(SkyBlockCalendar.getDay())); - lines.add("§7 " + SkyBlockCalendar.getDisplay(SkyBlockCalendar.getElapsed())); + List lines = new ArrayList<>(); + lines.add(Component.empty() + .append(Component.text(new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()), NamedTextColor.GRAY)) + .append(Component.text(" " + HypixelConst.getServerName(), NamedTextColor.DARK_GRAY))); + lines.add(Component.text(" ", NamedTextColor.GRAY)); + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.WHITE)) + .append(Component.text(SkyBlockCalendar.getMonthName() + " " + StringUtility.ntify(SkyBlockCalendar.getDay()), NamedTextColor.WHITE))); + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.GRAY)) + .append(Component.text(SkyBlockCalendar.getDisplay(SkyBlockCalendar.getElapsed()), NamedTextColor.GRAY))); try { - RegionType type = region.getType(); - String name = type.getName(); + RegionType type = Objects.requireNonNull(region).getType(); + String name = type.getColor() + type.getName(); if (type == RegionType.PLAYER_MUSEUM) { name = name.formatted(player.getUsername()); } - lines.add("§7 ⏣ " + region.getType().getColor() + name); + lines.add(Component.empty() + .append(Component.text(" ⏣ ", NamedTextColor.GRAY)) + .append(Component.text(name))); } catch (NullPointerException ignored) { - lines.add(" " + I18n.string("scoreboard.skyblock.region_unknown")); + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.WHITE)) + .append(I18n.t("scoreboard.skyblock.region_unknown"))); } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skyblock.purse_label") + StringUtility.commaify(dataHandler.get(SkyBlockDataHandler.Data.COINS, DatapointDouble.class).getValue())); - lines.add(I18n.string("scoreboard.skyblock.bits_label") + StringUtility.commaify(dataHandler.get(SkyBlockDataHandler.Data.BITS, DatapointInteger.class).getValue())); + lines.add(Component.text(" ", NamedTextColor.GRAY)); + + lines.add(I18n.t("scoreboard.skyblock.purse", Component.text(StringUtility.commaify(dataHandler.get(SkyBlockDataHandler.Data.COINS, DatapointDouble.class).getValue())))); + if (HypixelConst.isGarden()) { + GardenData.GardenCoreData core = dataHandler.get(SkyBlockDataHandler.Data.GARDEN_CORE, DatapointGardenCore.class).getValue(); + GardenData.GardenPersonalData personal = dataHandler.get(SkyBlockDataHandler.Data.GARDEN_PERSONAL, DatapointGardenPersonal.class).getValue(); + lines.add(Component.text("Copper: ", NamedTextColor.WHITE) + .append(Component.text(StringUtility.commaify(core.getCopper()), NamedTextColor.RED))); + lines.add(Component.text("Sawdust: ", NamedTextColor.WHITE) + .append(Component.text(StringUtility.commaify(personal.getSowdust()), NamedTextColor.DARK_GREEN))); + } else { + lines.add(I18n.t("scoreboard.skyblock.bits", Component.text(StringUtility.commaify(dataHandler.get(SkyBlockDataHandler.Data.BITS, DatapointInteger.class).getValue())))); - if (DarkAuctionHandler.isPlayerInAuction(player.getUuid()) + if (DarkAuctionHandler.isPlayerInAuction(player.getUuid()) && DarkAuctionHandler.getLocalState() != null && DarkAuctionHandler.getLocalState().getPhase() == DarkAuctionPhase.BIDDING - ) { - lines.add("§8 "); - DarkAuctionHandler.DarkAuctionLocalState auctionState = DarkAuctionHandler.getLocalState(); - int timeRemaining = DarkAuctionHandler.getTimeLeft().get(); - - lines.add(I18n.string("scoreboard.skyblock.dark_auction.time_left_label") + timeRemaining + I18n.string("scoreboard.skyblock.dark_auction.time_left_suffix")); - lines.add(I18n.string("scoreboard.skyblock.dark_auction.current_item_label")); - - String currentItem = auctionState.getCurrentItemType(); - if (currentItem != null) { - try { - ItemType itemType = ItemType.valueOf(currentItem); - SkyBlockItem item = new SkyBlockItem(itemType); - lines.add(" " + item.getDisplayName()); - } catch (Exception e) { - lines.add(" §f" + currentItem.replace("_", " ")); + ) { + lines.add(Component.text(" ", NamedTextColor.DARK_GRAY)); + DarkAuctionHandler.DarkAuctionLocalState auctionState = DarkAuctionHandler.getLocalState(); + int timeRemaining = DarkAuctionHandler.getTimeLeft().get(); + + lines.add(Component.empty() + .append(I18n.t("scoreboard.skyblock.dark_auction.time_left_label")) + .append(Component.text(String.valueOf(timeRemaining))) + .append(I18n.t("scoreboard.skyblock.dark_auction.time_left_suffix"))); + lines.add(I18n.t("scoreboard.skyblock.dark_auction.current_item_label")); + + String currentItem = auctionState.getCurrentItemType(); + if (currentItem != null) { + try { + ItemType itemType = ItemType.valueOf(currentItem); + SkyBlockItem item = new SkyBlockItem(itemType); + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.WHITE)) + .append(Component.text(item.getDisplayName()))); + } catch (Exception e) { + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.WHITE)) + .append(Component.text(currentItem.replace("_", " "), NamedTextColor.WHITE))); + } + } else { + lines.add(Component.empty() + .append(Component.text(" ", NamedTextColor.WHITE)) + .append(I18n.t("scoreboard.skyblock.dark_auction.waiting"))); } } else { - lines.add(" " + I18n.string("scoreboard.skyblock.dark_auction.waiting")); - } - } else { - if (region != null && + if (region != null && !missionData.getActiveMissions(region.getType()).isEmpty()) { - lines.add("§7 "); - MissionData.ActiveMission mission = missionData.getActiveMissions(region.getType()).getFirst(); - SkyBlockMission skyBlockMission = MissionData.getMissionClass(mission.getMissionID()); - - if (skyBlockMission instanceof LocationAssociatedMission locationAssociatedMission) { - lines.add(I18n.string("scoreboard.skyblock.objective_label") + " " + BlockUtility.getArrow( - player.getPosition(), - locationAssociatedMission.getLocation() - )); - lines.add("§e" + mission); - } else { - lines.add(I18n.string("scoreboard.skyblock.objective_label")); - lines.add("§e" + mission); + lines.add(Component.text(" ", NamedTextColor.GRAY)); + MissionData.ActiveMission mission = missionData.getActiveMissions(region.getType()).getFirst(); + SkyBlockMission skyBlockMission = MissionData.getMissionClass(mission.getMissionID()); + + if (skyBlockMission instanceof LocationAssociatedMission locationAssociatedMission) { + lines.add(Component.empty() + .append(I18n.t("scoreboard.skyblock.objective_label")) + .append(Component.text(" " + BlockUtility.getArrow( + player.getPosition(), + locationAssociatedMission.getLocation() + )))); + } else { + lines.add(I18n.t("scoreboard.skyblock.objective_label")); + } + lines.add(Component.text(String.valueOf(mission), NamedTextColor.YELLOW)); + + SkyBlockProgressMission progressMission = missionData.getAsProgressMission(mission.getMissionID()); + if (progressMission != null) + lines.add(Component.empty() + .append(Component.text(" (", NamedTextColor.GRAY)) + .append(Component.text(String.valueOf(mission.getMissionProgress()), NamedTextColor.YELLOW)) + .append(Component.text("/", NamedTextColor.GRAY)) + .append(Component.text(String.valueOf(progressMission.getMaxProgress()), NamedTextColor.GREEN)) + .append(Component.text(")", NamedTextColor.GRAY))); } - - SkyBlockProgressMission progressMission = missionData.getAsProgressMission(mission.getMissionID()); - if (progressMission != null) - lines.add("§7 (§e" + mission.getMissionProgress() + "§7/§a" + progressMission.getMaxProgress() + "§7)"); } } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + lines.add(Component.text(" ", NamedTextColor.GRAY)); + lines.add(I18n.t("scoreboard.common.footer")); - String title = " " + getSidebarName(skyblockName, false) - + (player.isCoop() ? " " + I18n.string("scoreboard.skyblock.coop_suffix") + " " : " "); + Component title = Component.empty() + .append(Component.text(" ")) + .append(HypixelScoreboard.getSidebarName(I18n.string("scoreboard.skyblock.title_base"), skyblockName, false)) + .append(Component.text(" ")); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, title); @@ -139,22 +182,4 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter, boolean isGuest) { - String baseText = I18n.string("scoreboard.skyblock.title_base"); - String[] colors = {"§f§l", "§6§l", "§e§l"}; - String endColor = "§a§l"; - String endText = isGuest ? " GUEST" : ""; - - if (counter > 0 && counter <= 8) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter) + - endColor + endText; - } else if ((counter >= 9 && counter <= 19) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText + endColor + endText; - } else { - return colors[2] + baseText + endColor + endText; - } - } } diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/warps/TravelScrollIslands.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/warps/TravelScrollIslands.java index e480e75d2..cbe72271b 100644 --- a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/warps/TravelScrollIslands.java +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/warps/TravelScrollIslands.java @@ -15,6 +15,9 @@ public enum TravelScrollIslands { PRIVATE_ISLAND("home", "§bPrivate Island", "f151cffdaf303673531a7651b36637cad912ba485643158e548d59b2ead5011", (unused) -> "Your very own chunk of SkyBlock. Nice housing for your minions.", ServerType.SKYBLOCK_ISLAND), + GARDEN("garden", "§aThe Garden", + "7a7d8f7a5f36b6e8d6fbfe2f3ed5de1a260a6ef3c928b5b70d7f0a4f1fb2f6f0", + (unused) -> "A private farming island with editable plots, visitors, pests, and Garden progression.", ServerType.SKYBLOCK_GARDEN), SKYBLOCK_HUB("hub", "§bSkyBlock Hub", "9c465a5d348c53d473f8115ed8923be416f35149f73ebaf5f2b05e13401e814f", (unused) -> "Where everything happens and anything is possible.", ServerType.SKYBLOCK_HUB, List.of(TravelScrollType.HUB_CASTLE, TravelScrollType.HUB_MUSEUM, TravelScrollType.HUB_CRYPTS, TravelScrollType.HUB_DARK_AUCTION)), GOLD_MINE("gold", "§aGold Mine §7- §bSpawn", diff --git a/type.skywarsgame/src/main/java/net/swofty/type/skywarsgame/SkywarsGameScoreboard.java b/type.skywarsgame/src/main/java/net/swofty/type/skywarsgame/SkywarsGameScoreboard.java index f58253b02..a7c5b4486 100644 --- a/type.skywarsgame/src/main/java/net/swofty/type/skywarsgame/SkywarsGameScoreboard.java +++ b/type.skywarsgame/src/main/java/net/swofty/type/skywarsgame/SkywarsGameScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.skywarsgame; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -9,7 +11,6 @@ import net.swofty.type.generic.i18n.I18n; import net.swofty.type.generic.scoreboard.HypixelScoreboard; import net.swofty.type.generic.user.HypixelPlayer; -import net.swofty.type.skywarsgame.TypeSkywarsGameLoader; import net.swofty.type.skywarsgame.game.SkywarsGame; import net.swofty.type.skywarsgame.game.SkywarsGameStatus; import net.swofty.type.skywarsgame.user.SkywarsPlayer; @@ -39,9 +40,9 @@ public static void start() { SkywarsPlayer swPlayer = (SkywarsPlayer) player; - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); if (game.getGameStatus() == SkywarsGameStatus.IN_PROGRESS) { long elapsedMs = System.currentTimeMillis() - game.getGameStartTime(); @@ -49,17 +50,17 @@ public static void start() { String nextEventLine = getNextEventLine(game.getCurrentEvent(), elapsedSeconds); if (nextEventLine != null) { - lines.add(I18n.string("scoreboard.skywars_game.next_event_label")); - lines.add(nextEventLine); - lines.add("§7 "); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.next_event_label"))); + lines.add(HypixelScoreboard.legacy(nextEventLine)); + lines.add(HypixelScoreboard.legacy("§7 ")); } int alive = (int) game.getPlayers().stream().filter(p -> !p.isEliminated()).count(); - lines.add(I18n.string("scoreboard.skywars_game.players_left_label") + alive); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_game.kills_label") + swPlayer.getKillsThisGame()); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.players_left_label") + alive)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.kills_label") + swPlayer.getKillsThisGame())); } else if (game.getGameStatus() == SkywarsGameStatus.ENDING) { - lines.add(I18n.string("scoreboard.skywars_game.top_killers_label")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.top_killers_label"))); java.util.List topKillers = game.getPlayers().stream() .sorted((a, b) -> Integer.compare(b.getKillsThisGame(), a.getKillsThisGame())) @@ -73,26 +74,26 @@ public static void start() { }; for (int i = 0; i < topKillers.size(); i++) { SkywarsPlayer killer = topKillers.get(i); - lines.add(places[i] + " §f" + killer.getUsername() + " §7- §a" + killer.getKillsThisGame()); + lines.add(HypixelScoreboard.legacy(places[i] + " §f" + killer.getUsername() + " §7- §a" + killer.getKillsThisGame())); } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_game.your_kills_label") + swPlayer.getKillsThisGame()); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.your_kills_label") + swPlayer.getKillsThisGame())); } else if (game.getGameStatus() == SkywarsGameStatus.WAITING) { - lines.add(I18n.string("scoreboard.skywars_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_game.waiting")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.waiting"))); } else if (game.getGameStatus() == SkywarsGameStatus.STARTING) { - lines.add(I18n.string("scoreboard.skywars_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_game.starting_in_label") + game.getCountdown().getSecondsRemaining() + I18n.string("scoreboard.skywars_game.starting_in_suffix")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.players_label") + game.getPlayers().size() + "/" + game.getGameType().getMaxPlayers())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.starting_in_label") + game.getCountdown().getSecondsRemaining() + I18n.string("scoreboard.skywars_game.starting_in_suffix"))); } - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_game.map_label") + game.getMapEntry().getName()); - lines.add(I18n.string("scoreboard.skywars_game.mode_label") + game.getGameType().getDisplayName()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.map_label") + game.getMapEntry().getName())); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_game.mode_label") + game.getGameType().getDisplayName())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(animationFrame)); @@ -109,20 +110,22 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.skywars_game.title_base"); - String[] colors = {"§f§l", "§e§l", "§6§l"}; - - if (counter > 0 && counter <= 7) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter); - } else if ((counter >= 8 && counter <= 18) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText; - } else { - return colors[1] + baseText; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.skywars_game.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + NamedTextColor.GOLD, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 7, + 8, + 18, + 25, + 29, + Component.empty() + ); } private static String getNextEventLine(SkywarsGame.GameEvent currentEvent, long elapsedSeconds) { diff --git a/type.skywarslobby/src/main/java/net/swofty/type/skywarslobby/SkywarsLobbyScoreboard.java b/type.skywarslobby/src/main/java/net/swofty/type/skywarslobby/SkywarsLobbyScoreboard.java index c350d172a..d3a2129aa 100644 --- a/type.skywarslobby/src/main/java/net/swofty/type/skywarslobby/SkywarsLobbyScoreboard.java +++ b/type.skywarslobby/src/main/java/net/swofty/type/skywarslobby/SkywarsLobbyScoreboard.java @@ -1,5 +1,7 @@ package net.swofty.type.skywarslobby; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.timer.Scheduler; @@ -7,7 +9,6 @@ import net.swofty.commons.skywars.SkywarsLeaderboardMode; import net.swofty.commons.skywars.SkywarsLeaderboardPeriod; import net.swofty.commons.skywars.SkywarsLevelColor; -import net.swofty.type.skywarslobby.level.SkywarsLevelRegistry; import net.swofty.commons.skywars.SkywarsModeStats; import net.swofty.type.generic.HypixelConst; import net.swofty.type.generic.HypixelGenericLoader; @@ -17,6 +18,7 @@ import net.swofty.type.generic.i18n.I18n; import net.swofty.type.generic.scoreboard.HypixelScoreboard; import net.swofty.type.generic.user.HypixelPlayer; +import net.swofty.type.skywarslobby.level.SkywarsLevelRegistry; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -60,21 +62,21 @@ public static void start() { int level = SkywarsLevelRegistry.calculateLevel(experience); - List lines = new ArrayList<>(); - lines.add("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName()); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_lobby.your_level_label") + " " + SkywarsLevelColor.getLevelDisplay(level)); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_lobby.solo_kills_label") + soloKills); - lines.add(I18n.string("scoreboard.skywars_lobby.solo_wins_label") + soloWins); - lines.add(I18n.string("scoreboard.skywars_lobby.doubles_kills_label") + doublesKills); - lines.add(I18n.string("scoreboard.skywars_lobby.doubles_wins_label") + doublesWins); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.skywars_lobby.coins_label") + coins); - lines.add(I18n.string("scoreboard.skywars_lobby.souls_label") + souls); - lines.add(I18n.string("scoreboard.skywars_lobby.tokens_label") + tokens); - lines.add("§7 "); - lines.add(I18n.string("scoreboard.common.footer")); + List lines = new ArrayList<>(); + lines.add(HypixelScoreboard.legacy("§7" + new SimpleDateFormat(I18n.string("scoreboard.common.date_format")).format(new Date()) + " §8" + HypixelConst.getServerName())); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.your_level_label") + " " + SkywarsLevelColor.getLevelDisplay(level))); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.solo_kills_label") + soloKills)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.solo_wins_label") + soloWins)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.doubles_kills_label") + doublesKills)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.doubles_wins_label") + doublesWins)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.coins_label") + coins)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.souls_label") + souls)); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.skywars_lobby.tokens_label") + tokens)); + lines.add(HypixelScoreboard.legacy("§7 ")); + lines.add(HypixelScoreboard.legacy(I18n.string("scoreboard.common.footer"))); if (!scoreboard.hasScoreboard(player)) { scoreboard.createScoreboard(player, getSidebarName(animationFrame)); @@ -91,19 +93,21 @@ public static void removeCache(Player player) { scoreboard.removeScoreboard(player); } - private static String getSidebarName(int counter) { - String baseText = I18n.string("scoreboard.skywars_lobby.title_base"); - String[] colors = {"§f§l", "§e§l", "§6§l"}; - - if (counter > 0 && counter <= 7) { - return colors[0] + baseText.substring(0, counter - 1) + - colors[1] + baseText.charAt(counter - 1) + - colors[2] + baseText.substring(counter); - } else if ((counter >= 8 && counter <= 18) || - (counter >= 25 && counter <= 29)) { - return colors[0] + baseText; - } else { - return colors[1] + baseText; - } + private static Component getSidebarName(int counter) { + return HypixelScoreboard.animatedSidebarName( + I18n.string("scoreboard.skywars_lobby.title_base"), + counter, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + NamedTextColor.GOLD, + NamedTextColor.WHITE, + NamedTextColor.YELLOW, + 7, + 8, + 18, + 25, + 29, + Component.empty() + ); } } diff --git a/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCGrandmaWolf.java b/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCGrandmaWolf.java index b88fe2bef..10bcc946d 100644 --- a/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCGrandmaWolf.java +++ b/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCGrandmaWolf.java @@ -7,7 +7,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCGrandmaWolf extends HypixelNPC { +public class NPCGrandmaWolf extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCGrandmaWolf() { super(new HumanConfiguration() { @Override @@ -44,4 +44,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { } + + @Override + public String gardenSpokenNpcId() { + return "GRANDMA_WOLF"; + } } diff --git a/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCShaggy.java b/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCShaggy.java index 89aa8860b..202b6c2e3 100644 --- a/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCShaggy.java +++ b/type.spidersden/src/main/java/net/swofty/type/spidersden/npcs/NPCShaggy.java @@ -7,7 +7,7 @@ import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; -public class NPCShaggy extends HypixelNPC { +public class NPCShaggy extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCShaggy() { super(new HumanConfiguration() { @Override @@ -43,4 +43,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent event) { } + + @Override + public String gardenSpokenNpcId() { + return "SHAGGY"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCBeth.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCBeth.java index 382d187a7..b35b8d24c 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCBeth.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCBeth.java @@ -3,13 +3,13 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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.progression.GardenSpokenNpcSource; -public class NPCBeth extends HypixelNPC { +public class NPCBeth extends HypixelNPC implements GardenSpokenNpcSource { public NPCBeth() { super(new HumanConfiguration() { @Override @@ -43,4 +43,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "BETH"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFarmerJon.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFarmerJon.java index 5aeaade88..1a2938c03 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFarmerJon.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFarmerJon.java @@ -3,13 +3,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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; -public class NPCFarmerJon extends HypixelNPC { +public class NPCFarmerJon extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCFarmerJon() { super(new HumanConfiguration() { @Override @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "FARMER_JON"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFriendlyHiker.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFriendlyHiker.java index ef45b85bb..ba6d638fd 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFriendlyHiker.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCFriendlyHiker.java @@ -3,13 +3,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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; -public class NPCFriendlyHiker extends HypixelNPC { +public class NPCFriendlyHiker extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCFriendlyHiker() { super(new HumanConfiguration() { @Override @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "FRIENDLY_HIKER"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCHungryHiker.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCHungryHiker.java index 7621ee088..cc8171805 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCHungryHiker.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCHungryHiker.java @@ -3,11 +3,13 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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.progression.GardenProgressionReward; +import net.swofty.type.skyblockgeneric.garden.progression.GardenProgressionSupport; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; public class NPCHungryHiker extends HypixelNPC { public NPCHungryHiker() { @@ -41,6 +43,11 @@ public boolean looking(HypixelPlayer player) { @Override public void onClick(NPCInteractEvent e) { + if (e.player() instanceof SkyBlockPlayer player) { + GardenProgressionSupport.apply(player, GardenProgressionReward.flag("SAVE_THIS_HIKER")); + sendNPCMessage(player, "Thanks for stopping by. I feel a lot safer now."); + return; + } e.player().notImplemented(); } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCMason.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCMason.java index 2bce7355c..d2cb7ca64 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCMason.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCMason.java @@ -3,13 +3,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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; -public class NPCMason extends HypixelNPC { +public class NPCMason extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCMason() { super(new HumanConfiguration() { @Override @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "MASON"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTammy.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTammy.java index efebdc63f..0ee358c2a 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTammy.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTammy.java @@ -3,13 +3,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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; -public class NPCTammy extends HypixelNPC { +public class NPCTammy extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCTammy() { super(new HumanConfiguration() { @Override @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "TAMMY"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTrevorTheTrapper.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTrevorTheTrapper.java index c9ed4000f..a76e06654 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTrevorTheTrapper.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/NPCTrevorTheTrapper.java @@ -3,13 +3,12 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.minestom.server.coordinate.Pos; -import net.swofty.type.generic.user.HypixelPlayer; 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; -public class NPCTrevorTheTrapper extends HypixelNPC { +public class NPCTrevorTheTrapper extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public NPCTrevorTheTrapper() { super(new HumanConfiguration() { @Override @@ -43,4 +42,9 @@ public boolean looking(HypixelPlayer player) { public void onClick(NPCInteractEvent e) { e.player().notImplemented(); } + + @Override + public String gardenSpokenNpcId() { + return "TREVOR_THE_TRAPPER"; + } } diff --git a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/villagers/VillagerFarmHand.java b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/villagers/VillagerFarmHand.java index 69f39c1bb..2381e55bb 100644 --- a/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/villagers/VillagerFarmHand.java +++ b/type.thefarmingislands/src/main/java/net/swofty/type/thefarmingislands/npcs/villagers/VillagerFarmHand.java @@ -4,6 +4,7 @@ import net.minestom.server.entity.VillagerProfession; import net.swofty.type.generic.entity.npc.HypixelNPC; import net.swofty.type.generic.entity.npc.configuration.VillagerConfiguration; +import net.swofty.type.generic.event.custom.NPCInteractEvent; import net.swofty.type.generic.user.HypixelPlayer; import net.swofty.type.skyblockgeneric.mission.MissionData; import net.swofty.type.skyblockgeneric.mission.missions.barn.MissionCraftWheatMinion; @@ -13,9 +14,7 @@ import java.util.List; -import net.swofty.type.generic.event.custom.NPCInteractEvent; - -public class VillagerFarmHand extends HypixelNPC { +public class VillagerFarmHand extends HypixelNPC implements net.swofty.type.skyblockgeneric.garden.progression.GardenSpokenNpcSource { public VillagerFarmHand() { super(new VillagerConfiguration(){ @Override @@ -88,4 +87,9 @@ public void onClick(NPCInteractEvent e) { } setDialogue(e.player(), "initial-hello"); } + + @Override + public String gardenSpokenNpcId() { + return "FARMHAND"; + } } diff --git a/velocity.extension/src/main/java/net/swofty/velocity/SkyBlockVelocity.java b/velocity.extension/src/main/java/net/swofty/velocity/SkyBlockVelocity.java index b8f5d4033..91187f797 100644 --- a/velocity.extension/src/main/java/net/swofty/velocity/SkyBlockVelocity.java +++ b/velocity.extension/src/main/java/net/swofty/velocity/SkyBlockVelocity.java @@ -49,6 +49,7 @@ import net.swofty.proxyapi.redis.ServerOutboundMessage; import net.swofty.redisapi.api.RedisAPI; import net.swofty.velocity.command.LimboCommand; +import net.swofty.velocity.command.LobbyCommand; import net.swofty.velocity.command.ProtocolVersionCommand; import net.swofty.velocity.command.ServerStatusCommand; import net.swofty.velocity.data.CoopDatabase; @@ -196,6 +197,13 @@ public void onProxyInitialization(ProxyInitializeEvent event) { commandManager.register(limboCommandMeta, new LimboCommand()); + CommandMeta lobbyCommandMeta = commandManager.metaBuilder("lobby") + .plugin(this) + .aliases("l") + .build(); + + commandManager.register(lobbyCommandMeta, new LobbyCommand()); + // Handle database new ProfilesDatabase("_placeHolder").connect(ConfigProvider.settings().getMongodb()); UserDatabase.connect(ConfigProvider.settings().getMongodb()); @@ -343,7 +351,7 @@ public void onServerCrash(KickedFromServerEvent event) { )); TransferHandler transferHandler = new TransferHandler(event.getPlayer()); - transferHandler.transferTo(serverType); + transferHandler.queueTransferAfterCurrentServer(serverType); CompletableFuture.delayedExecutor(GameManager.SLEEP_TIME + 300, TimeUnit.MILLISECONDS) .execute(() -> { diff --git a/velocity.extension/src/main/java/net/swofty/velocity/command/LobbyCommand.java b/velocity.extension/src/main/java/net/swofty/velocity/command/LobbyCommand.java new file mode 100644 index 000000000..96fc5e0a6 --- /dev/null +++ b/velocity.extension/src/main/java/net/swofty/velocity/command/LobbyCommand.java @@ -0,0 +1,19 @@ +package net.swofty.velocity.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import net.swofty.commons.ServerType; +import net.swofty.velocity.gamemanager.TransferHandler; + +public class LobbyCommand implements SimpleCommand { + @Override + public void execute(Invocation invocation) { + CommandSource source = invocation.source(); + if (!(source instanceof Player player)) { + return; + } + TransferHandler transferHandler = new TransferHandler(player); + transferHandler.transferTo(ServerType.PROTOTYPE_LOBBY); + } +} \ No newline at end of file diff --git a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java index 14b991b71..b540defc2 100644 --- a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java +++ b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/BalanceConfigurations.java @@ -7,7 +7,6 @@ import net.swofty.velocity.gamemanager.balanceconfigurations.ReadyGames; import net.swofty.velocity.testflow.TestFlowManager; import org.jetbrains.annotations.Nullable; -import org.tinylog.Logger; import java.util.HashMap; import java.util.List; @@ -58,7 +57,9 @@ public class BalanceConfigurations { Map.entry(ServerType.SKYBLOCK_JERRYS_WORKSHOP, List.of( new LowestPlayerCount() )), - + Map.entry(ServerType.SKYBLOCK_GARDEN, List.of( + new LowestPlayerCount() + )), Map.entry(ServerType.PROTOTYPE_LOBBY, List.of( new LowestPlayerCount() )), @@ -81,7 +82,6 @@ public class BalanceConfigurations { Map.entry(ServerType.MURDER_MYSTERY_GAME, List.of( new ReadyGames() )), - Map.entry(ServerType.SKYWARS_CONFIGURATOR, List.of( new LowestPlayerCount() )), @@ -97,13 +97,22 @@ public class BalanceConfigurations { )); public static @Nullable GameManager.GameServer getServerFor(Player player, ServerType type) { + if (type == null) { + return null; + } + + List typeConfigurations = configurations.get(type); + if (typeConfigurations == null || typeConfigurations.isEmpty()) { + return null; + } + if (TestFlowManager.isPlayerInTestFlow(player.getUsername())) { player.sendPlainMessage("§eYou are currently in a network-isolated test flow, load balancing will be restricted to test flow servers!"); player.sendPlainMessage("§8Executing test flow " + TestFlowManager.getTestFlowForPlayer(player.getUsername()).getName() + "..."); } try { - for (BalanceConfiguration configuration : configurations.get(type)) { + for (BalanceConfiguration configuration : typeConfigurations) { List serversToConsider = GameManager.getFromType(type); if (TestFlowManager.isPlayerInTestFlow(player.getUsername())) { serversToConsider.removeIf(server -> { @@ -146,7 +155,7 @@ public class BalanceConfigurations { } return null; } catch (Exception e) { - System.out.println("Error in trying to balance type " + type.name() + " for player " + player.getUsername()); + System.out.println("Error in trying to balance type " + String.valueOf(type) + " for player " + player.getUsername()); throw e; } } diff --git a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/TransferHandler.java b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/TransferHandler.java index b1af68836..b7b356abe 100644 --- a/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/TransferHandler.java +++ b/velocity.extension/src/main/java/net/swofty/velocity/gamemanager/TransferHandler.java @@ -9,17 +9,16 @@ import net.swofty.velocity.redis.RedisMessage; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; public record TransferHandler(Player player) { - public static final Map playersGoalServerType = new HashMap<>(); - private static final Map playersOriginServer = new HashMap<>(); - private static final List disregard = new ArrayList<>(); + public static final Map playersGoalServerType = new ConcurrentHashMap<>(); + private static final Map playersOriginServer = new ConcurrentHashMap<>(); + private static final Set disregard = ConcurrentHashMap.newKeySet(); public boolean isInLimbo() { return playersGoalServerType.containsKey(player); @@ -50,21 +49,29 @@ public CompletableFuture sendToLimbo() { return future; } + public void queueTransferAfterCurrentServer(ServerType type) { + RegisteredServer originServer = playersOriginServer.get(player); + if (originServer == null) { + player.getCurrentServer().ifPresent(conn -> playersOriginServer.put(player, conn.getServer())); + } + playersGoalServerType.put(player, type); + } + public void previousServerIsFinished(RegisteredServer manualPick) { new Thread(() -> { if (disregard.contains(player)) return; RegisteredServer originServer = playersOriginServer.get(player); - ServerType originServerType = GameManager.getTypeFromRegisteredServer(originServer); + ServerType originServerType = originServer != null ? GameManager.getTypeFromRegisteredServer(originServer) : null; UUID serverUUID = UUID.fromString(manualPick.getServerInfo().getName()); - UUID originServerUUID = UUID.fromString(originServer.getServerInfo().getName()); - - RedisMessage.sendMessageToServer(serverUUID, + if (originServerType != null) { + RedisMessage.sendMessageToServer(serverUUID, FromProxyChannels.GIVE_PLAYERS_ORIGIN_TYPE, new JSONObject().put("uuid", player.getUniqueId().toString()) - .put("origin-type", originServerType.name()) - ); + .put("origin-type", originServerType.name()) + ); + } playersGoalServerType.remove(player); playersOriginServer.remove(player); @@ -73,9 +80,12 @@ public void previousServerIsFinished(RegisteredServer manualPick) { player.sendMessage(Component.text("§7Sending to server " + manualPickAsGame.displayName() + "...")); player.createConnectionRequest(manualPick).connectWithIndication(); - RedisMessage.sendMessageToServer(originServerUUID, + if (originServer != null) { + UUID originServerUUID = UUID.fromString(originServer.getServerInfo().getName()); + RedisMessage.sendMessageToServer(originServerUUID, FromProxyChannels.PLAYER_HAS_SWITCHED_FROM_HERE, new JSONObject().put("uuid", player.getUniqueId().toString())); + } }).start(); } @@ -84,23 +94,31 @@ public void previousServerIsFinished() { if (disregard.contains(player)) return; ServerType type = playersGoalServerType.get(player); + if (type == null) { + forceRemoveFromLimbo(); + player.disconnect(Component.text("§cYour transfer state expired before the previous server finished. Please reconnect.")); + return; + } + GameManager.GameServer server = BalanceConfigurations.getServerFor(player, type); if (server == null) { + forceRemoveFromLimbo(); player.disconnect(Component.text("§cThere are no Hypixel (type=" + type.name() + ") servers available at the moment.")); return; } RegisteredServer originServer = playersOriginServer.get(player); - UUID originServerUUID = UUID.fromString(originServer.getServerInfo().getName()); UUID sendingToServerUUID = server.internalID(); - ServerType originServerType = GameManager.getTypeFromRegisteredServer(originServer); + ServerType originServerType = originServer != null ? GameManager.getTypeFromRegisteredServer(originServer) : null; - RedisMessage.sendMessageToServer(sendingToServerUUID, + if (originServerType != null) { + RedisMessage.sendMessageToServer(sendingToServerUUID, FromProxyChannels.GIVE_PLAYERS_ORIGIN_TYPE, new JSONObject().put("uuid", player.getUniqueId().toString()) - .put("origin-type", originServerType.name()) - ); + .put("origin-type", originServerType.name()) + ); + } playersOriginServer.remove(player); playersGoalServerType.remove(player); @@ -108,9 +126,12 @@ public void previousServerIsFinished() { player.sendMessage(Component.text("§7Sending to server " + server.displayName() + "...")); player.createConnectionRequest(server.registeredServer()).connectWithIndication(); - RedisMessage.sendMessageToServer(originServerUUID, + if (originServer != null) { + UUID originServerUUID = UUID.fromString(originServer.getServerInfo().getName()); + RedisMessage.sendMessageToServer(originServerUUID, FromProxyChannels.PLAYER_HAS_SWITCHED_FROM_HERE, new JSONObject().put("uuid", player.getUniqueId().toString())); + } }).start(); } @@ -121,7 +142,7 @@ public void transferTo(ServerType type) { player.getCurrentServer().ifPresent(conn -> playersOriginServer.put(player, conn.getServer())); originServer = playersOriginServer.get(player); } - ServerType originServerType = GameManager.getTypeFromRegisteredServer(originServer); + ServerType originServerType = originServer != null ? GameManager.getTypeFromRegisteredServer(originServer) : null; playersGoalServerType.remove(player); playersOriginServer.remove(player); diff --git a/velocity.extension/src/main/java/net/swofty/velocity/redis/listeners/ListenerServerFinishedWithPlayer.java b/velocity.extension/src/main/java/net/swofty/velocity/redis/listeners/ListenerServerFinishedWithPlayer.java index 834531e79..35b0b5801 100644 --- a/velocity.extension/src/main/java/net/swofty/velocity/redis/listeners/ListenerServerFinishedWithPlayer.java +++ b/velocity.extension/src/main/java/net/swofty/velocity/redis/listeners/ListenerServerFinishedWithPlayer.java @@ -1,6 +1,7 @@ package net.swofty.velocity.redis.listeners; import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; import net.swofty.commons.proxy.ToProxyChannels; import net.swofty.velocity.SkyBlockVelocity; import net.swofty.velocity.gamemanager.TransferHandler; @@ -16,6 +17,8 @@ public class ListenerServerFinishedWithPlayer extends RedisListener { @Override public JSONObject receivedMessage(JSONObject message, UUID serverUUID) { UUID playerUUID = UUID.fromString(message.getString("uuid")); + boolean success = message.optBoolean("success", true); + String reason = message.optString("reason", "Your player data could not be saved safely. Please reconnect."); Optional potentialPlayer = SkyBlockVelocity.getServer().getPlayer(playerUUID); if (potentialPlayer.isEmpty()) { @@ -24,6 +27,16 @@ public JSONObject receivedMessage(JSONObject message, UUID serverUUID) { Player player = potentialPlayer.get(); TransferHandler handler = new TransferHandler(player); + if (!success) { + handler.forceRemoveFromLimbo(); + player.disconnect(Component.text("§c" + reason)); + return new JSONObject(); + } + + if (!handler.isInLimbo()) { + return new JSONObject(); + } + handler.previousServerIsFinished(); return new JSONObject();