diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..ab1f4164e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/build.gradle.kts b/build.gradle.kts index b202e2921..3ee2091f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,6 +8,10 @@ plugins { group = "net.swofty" version = "1.0" +repositories { + mavenCentral() +} + subprojects { apply(plugin = "java") apply(plugin = "java-library") diff --git a/service.darkauction/build.gradle.kts b/service.darkauction/build.gradle.kts index fe68e8dec..2345b4d5e 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.7" + id("com.gradleup.shadow") version "9.3.1" } group = "net.swofty" diff --git a/service.orchestrator/build.gradle.kts b/service.orchestrator/build.gradle.kts index f6c404e17..00b3e220c 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.7" + id("com.gradleup.shadow") version "9.3.1" } group = "net.swofty" 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 df3fda2b4..3f6d7e574 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 @@ -42,7 +42,8 @@ public abstract class HypixelNPC { public HypixelNPC(NPCConfiguration configuration) { this.parameters = configuration; String className = getClass().getSimpleName().replace("NPC", "").replace("Villager", ""); - this.name = parameters.chatName() != null ? parameters.chatName() : className.replaceAll("(?<=.)(?=\\p{Lu})", " "); + String defaultName = className.replaceAll("(?<=.)(?=\\p{Lu})", " "); + this.name = parameters.chatName() != null ? parameters.chatName() : defaultName; this.dialogueController = new DialogueController(this); } @@ -85,9 +86,22 @@ public static void updateForPlayer(HypixelPlayer player) { HypixelNPC.getRegisteredNPCs().forEach((npc) -> { // If the main username can't be used (over 16 chars), use a blank space instead and use holograms for all lines boolean playerHasNPC = cache.getEntityImpls().containsKey(npc); + NPCConfiguration config = npc.getParameters(); + + // Check visibility - if not visible, skip creation or remove existing + if (!config.visible(player)) { + if (playerHasNPC) { + // Remove NPC that is no longer visible + Entity entity = cache.get(npc).getValue(); + PlayerHolograms.ExternalPlayerHologram holo = cache.get(npc).getKey(); + entity.remove(); + PlayerHolograms.removeExternalPlayerHologram(holo); + cache.remove(npc); + } + return; + } if (!playerHasNPC) { - NPCConfiguration config = npc.getParameters(); String[] holograms = config.holograms(player); Pos position = config.position(player); @@ -141,7 +155,6 @@ public static void updateForPlayer(HypixelPlayer player) { Entity entity = cache.get(npc).getValue(); PlayerHolograms.ExternalPlayerHologram holo = cache.get(npc).getKey(); - NPCConfiguration config = npc.getParameters(); Pos npcPosition = config.position(player); String[] npcHolograms = config.holograms(player); @@ -245,10 +258,20 @@ public void sendNPCMessage(HypixelPlayer player, String message) { sendNPCMessage(player, message, Sound.sound().type(Key.key("entity.villager.celebrate")).volume(1.0f).pitch(0.8f + new Random().nextFloat() * 0.4f).build()); } - public void sendNPCMessage(HypixelPlayer player, String message, Sound sound) { - player.sendMessage("§e[NPC] " + getName() + "§f: " + message); - player.playSound(sound); - } + public void sendNPCMessage(HypixelPlayer player, String message, Sound sound) { + String displayName = getDisplayName(player); + player.sendMessage("§e[NPC] " + displayName + "§f: " + message); + player.playSound(sound); + } + + /** + * Gets the display name for this NPC for a specific player. + * Uses per-player chatName if available, otherwise falls back to default name. + */ + public String getDisplayName(HypixelPlayer player) { + String perPlayerName = parameters.chatName(player); + return perPlayerName != null ? perPlayerName : getName(); + } protected DialogueController dialogue() { return dialogueController; 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 76a7c0db3..78cebc545 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 @@ -25,6 +25,17 @@ default String chatName() { return null; } + /** + * Gets the chat name for this NPC specific to a player. + * Override this for NPCs that need per-player colored names. + * @param player The player viewing this NPC + * @return The formatted chat name, or null to use default + */ + @Nullable + default String chatName(HypixelPlayer player) { + return chatName(); + } + default Instance instance() { return HypixelConst.getInstanceContainer(); } 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..0ebb95518 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 @@ -6,6 +6,7 @@ import net.swofty.type.generic.event.HypixelEvent; import net.swofty.type.generic.event.HypixelEventClass; import net.swofty.type.generic.gui.v2.ViewNavigator; +import net.swofty.type.generic.gui.v2.ViewSession; import net.swofty.type.generic.user.HypixelPlayer; public class ActionInventoryOpen implements HypixelEventClass { @@ -14,7 +15,9 @@ 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) + .map(ViewNavigator::getCurrentSession) + .ifPresent(session -> session.onOpenEvent(event)); }); } 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 3fefd2471..570d8c91c 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 @@ -24,6 +24,8 @@ import net.swofty.type.generic.tab.TablistManager; import net.swofty.type.generic.tab.TablistModule; import net.swofty.type.hub.darkauction.DarkAuctionDisplay; +import net.swofty.type.hub.hoppity.HoppityHuntEntityManager; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityHuntManager; import net.swofty.type.hub.runes.RuneEntityImpl; import net.swofty.type.hub.tab.HubServerModule; import net.swofty.type.hub.util.HubMap; @@ -125,6 +127,11 @@ public void afterInitialize(MinecraftServer server) { // Place forest trees ForestTreePlacement.placeTrees(HypixelConst.getInstanceContainer()); + // Register Hoppity Hunt entity manager + HoppityHuntEntityManager hoppityEntityManager = new HoppityHuntEntityManager(); + HoppityHuntManager.setOnHuntStartCallback(hoppityEntityManager::spawnAllEggs); + HoppityHuntManager.setOnHuntStopCallback(hoppityEntityManager::despawnAllEggs); + HubMap hubMap = new HubMap(); hubMap.placeItemFrames(HypixelConst.getInstanceContainer()); } diff --git a/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityEggEntity.java b/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityEggEntity.java new file mode 100644 index 000000000..c6c55ee1b --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityEggEntity.java @@ -0,0 +1,59 @@ +package net.swofty.type.hub.hoppity; + +import net.minestom.server.component.DataComponents; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.entity.metadata.other.ArmorStandMeta; +import net.minestom.server.instance.Instance; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.network.player.ResolvableProfile; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityEggLocations; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityEggType; +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.ThreadLocalRandom; + +public class HoppityEggEntity extends EntityCreature { + private static final float MAX_YAW = 360f; + private static final double Y_SPAWN_OFFSET = 1.46875; + private static final String TEXTURE_URL_PREFIX = "http://textures.minecraft.net/texture/"; + + + private final HoppityEggLocations location; + private final HoppityEggType eggType; + + public HoppityEggEntity(HoppityEggLocations location, HoppityEggType eggType) { + super(EntityType.ARMOR_STAND); + this.location = location; + this.eggType = eggType; + + setInvisible(true); + ArmorStandMeta meta = (ArmorStandMeta) getEntityMeta(); + meta.setCustomNameVisible(false); + meta.setHasNoGravity(true); + meta.setSmall(true); + meta.setHasNoBasePlate(true); + meta.setNotifyAboutChanges(false); + + String texturesEncoded = encodeTexture(eggType.getTextureHash()); + setHelmet(ItemStack.builder(Material.PLAYER_HEAD) + .set(DataComponents.PROFILE, new ResolvableProfile(new PlayerSkin(texturesEncoded, null))) + .build()); + } + + public void spawn(Instance instance) { + float randomYaw = ThreadLocalRandom.current().nextFloat() * MAX_YAW; + setInstance(instance, location.getPosition().sub(0, Y_SPAWN_OFFSET, 0).withYaw(randomYaw)); + } + + private static String encodeTexture(String textureHash) { + JSONObject json = new JSONObject(); + json.put("textures", new JSONObject().put("SKIN", + new JSONObject().put("url", TEXTURE_URL_PREFIX + textureHash))); + return Base64.getEncoder().encodeToString(json.toString().getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityHuntEntityManager.java b/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityHuntEntityManager.java new file mode 100644 index 000000000..cddc69821 --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/hoppity/HoppityHuntEntityManager.java @@ -0,0 +1,54 @@ +package net.swofty.type.hub.hoppity; + +import net.swofty.type.generic.HypixelConst; +import net.swofty.type.generic.entity.InteractionEntity; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityEggLocations; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityEggType; +import net.swofty.type.skyblockgeneric.chocolatefactory.HoppityHuntManager; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class HoppityHuntEntityManager { + private static final float EGG_INTERACTION_WIDTH = 1.0f; + private static final float EGG_INTERACTION_HEIGHT = 1.0f; + + private final List spawnedEggs = new ArrayList<>(); + private final List spawnedInteractions = new ArrayList<>(); + + public void spawnAllEggs() { + HoppityHuntManager huntManager = HoppityHuntManager.getInstance(); + Map locationEggTypes = huntManager.getLocationEggTypes(); + + for (Map.Entry entry : locationEggTypes.entrySet()) { + HoppityEggLocations location = entry.getKey(); + HoppityEggType eggType = entry.getValue(); + + HoppityEggEntity eggEntity = new HoppityEggEntity(location, eggType); + eggEntity.spawn(HypixelConst.getInstanceContainer()); + spawnedEggs.add(eggEntity); + + InteractionEntity interaction = new InteractionEntity(EGG_INTERACTION_WIDTH, EGG_INTERACTION_HEIGHT, (player, event) -> { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + huntManager.claimEgg(skyBlockPlayer, location); + } + }); + interaction.setInstance(HypixelConst.getInstanceContainer(), location.getPosition()); + spawnedInteractions.add(interaction); + } + } + + public void despawnAllEggs() { + for (HoppityEggEntity egg : spawnedEggs) { + egg.remove(); + } + spawnedEggs.clear(); + + for (InteractionEntity interaction : spawnedInteractions) { + interaction.remove(); + } + spawnedInteractions.clear(); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/ChocolateFactoryRank.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/ChocolateFactoryRank.java new file mode 100644 index 000000000..15f442cba --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/ChocolateFactoryRank.java @@ -0,0 +1,66 @@ +package net.swofty.type.hub.npcs; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.swofty.commons.ChatColor; + +@Getter +@AllArgsConstructor +public enum ChocolateFactoryRank { + UNEMPLOYED(0, "Unemployed", ChatColor.RED), + INTERN(1, "Intern", ChatColor.GRAY), + EMPLOYEE(20, "Employee", ChatColor.GREEN), + ASSISTANT(120, "Assistant", ChatColor.BLUE), + MANAGER(140, "Manager", ChatColor.DARK_PURPLE), + DIRECTOR(180, "Director", ChatColor.GOLD), + EXECUTIVE(200, "Executive", ChatColor.LIGHT_PURPLE), + BOARD_MEMBER(220, "Board Member", ChatColor.AQUA); + + private final int level; + private final String name; + private final ChatColor color; + private static final ChocolateFactoryRank[] RANKS = values(); + + /** + * Gets the formatted hologram line for this rank using the rank's base level. + * Format: §7[Lvl X] §{color}RankName + * For UNEMPLOYED: §cUnemployed (no level) + */ + public String getHologramLine() { + return getHologramLine(level); + } + + /** + * Gets the formatted hologram line for this rank with a specific level. + * Format: §7[Lvl X] §{color}RankName + * For UNEMPLOYED: §cUnemployed (no level) + */ + public String getHologramLine(int actualLevel) { + if (this == UNEMPLOYED) { + return color + name; + } + return ChatColor.GRAY + "[" + actualLevel + "] " + color + name; + } + + /** + * Gets the formatted chat name for this rank. + * Format: §{color}NpcName + */ + public String getChatName(String npcName) { + return color + npcName; + } + + /** + * Gets the rank for a given level. + * Returns the highest rank the level qualifies for. + */ + public static ChocolateFactoryRank fromLevel(int level) { + ChocolateFactoryRank result = UNEMPLOYED; + for (ChocolateFactoryRank rank : RANKS) { + if (level >= rank.level) { + result = rank; + } + } + return result; + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCCoachJackrabbit.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCCoachJackrabbit.java new file mode 100644 index 000000000..e8737883d --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCCoachJackrabbit.java @@ -0,0 +1,59 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.gui.inventories.GUIChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.time.Duration; + +public class NPCCoachJackrabbit extends HypixelNPC { + + public NPCCoachJackrabbit() { + super(new HumanConfiguration() { + @Override + public String[] holograms(HypixelPlayer player) { + return new String[]{"§dCoach Jackrabbit", "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMzAyMjkyOTYwNCwKICAicHJvZmlsZUlkIiA6ICI2NGY0MGFiNzFmM2E0NGZiYjg0N2I5ZWFhOWZjNDRlNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJvZGF2aWRjZXNhciIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iYzBjYzY3ZTc5YzIyOGU1NDFlNjhhZWIxZDgxZWQ3YWY1MTE2NjYyMmFkNGRiOTQxN2Q3YTI5ZDFiODlhZjk1IgogICAgfQogIH0KfQ=="; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(-7.5, 69, 1.5, -74.5f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return "§dCoach Jackrabbit"; + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + ((SkyBlockPlayer) e.player()).openView(new GUIChocolateFactory()).refreshEvery(Duration.ofSeconds(1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return DialogueSet.EMPTY; + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitBro.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitBro.java new file mode 100644 index 000000000..0bafdf0b5 --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitBro.java @@ -0,0 +1,140 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitBro extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Bro"; + + public NPCRabbitBro() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + // Rabbit Bro is always visible (unlocked by default) + return true; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMjU5NDI0NjM2MywKICAicHJvZmlsZUlkIiA6ICJjZjc4YzFkZjE3ZTI0Y2Q5YTIxYmU4NWQ0NDk5ZWE4ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNYXR0c0FybW9yU3RhbmRzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzI4NzkzNGJkZDlkZjI3MDViMjUxYmI5OTdlMDI5YjE4YzFlOTRkZjEyOTkyYjgxMDdlNzQ0OTdiMjA1Y2E3ZTgiCiAgICB9CiAgfQp9"; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(8.5, 71, 19.5, 0f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 12 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "You should hire me! I can help you boost your §6Chocolate Factory §fproduction §dtenfold §fby next quarter!" + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "Hire me, bro!" + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "Get me a job at your §6Chocolate Factory§f!" + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "§aHoppity §fthinks he's all that with his chocolate empire.", + "But ask him this...who can hop the highest?" + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "Rise and grind, as I say. The early rabbit gets the cocoa beans." + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "§aHoppity §fmay run the factory, but who do you think inspires the work ethic?" + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "My morning routine? A quick hop around the fields, a bit of carrot juice, and then straight to work on the next big chocolate innovation." + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "Sleep is for the weak!" + }).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "Working out before dawn has its perks; you get to see §aHoppity§f's 'inspirational' morning pep talks.", + "It's like a shot of espresso, but with more hopping and less coffee." + }).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "§aHoppity's §fliving the sweet life now, but who's the one who taught him to dodge those garden gnomes? Bro knows best." + }).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "It's like a shot of espresso, but with more hopping and less coffee." + }).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "§aHoppity's §fliving the sweet life now, but who's the one who taught him to dodge those garden gnomes? Bro knows best." + }).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitCousin.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitCousin.java new file mode 100644 index 000000000..7ba0f17e9 --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitCousin.java @@ -0,0 +1,128 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitCousin extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Cousin"; + + public NPCRabbitCousin() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMjU5NDI2ODkxNCwKICAicHJvZmlsZUlkIiA6ICJlMjc5NjliODYyNWY0NDg1YjkyNmM5NTBhMDljMWMwMSIsCiAgInByb2ZpbGVOYW1lIiA6ICJLRVZJTktFTE9LRSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hOTgyODI1YzAxYjY1OGYzNDhhMDk5YjQ1NzkwMjlhMTgwZDJlNDE1MTgzOTUxYjJlNmU1ZTI3MjU3ZGY0MjU0IgogICAgfQogIH0KfQ=="; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(18.5, 69, 17.5, 71.7f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 8 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "My parents have been hounding me to get a job! Please hire me!" + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "I need a job soon, or my parents will kick me out of the warren." + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "I tell ya, family gatherings got a lot more interesting when §aHoppity§f started bringing those experimental chocolates.", + "Remember the carrot crunch debacle?" + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "Working hard or hardly working? With chocolate, it's both. I might drift in past noon, but when I'm on, I'm on fire.", + "They say genius often looks like laziness. Guess I'm living proof." + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "When I finally get to it, even §aGranny's§f impressed with the flavors I whip up." + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "I suggested chocolate-covered carrots to §aHoppity§f once. He laughed until he tried it. Now, who's laughing?", + "Still him, because it was a terrible idea." + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "My parents have been hounding me to get a job! Please hire me!" + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "I need a job soon, or my parents will kick me out of the warren." + }).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDaddy.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDaddy.java new file mode 100644 index 000000000..58664826c --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDaddy.java @@ -0,0 +1,155 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitDaddy extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Daddy"; + + public NPCRabbitDaddy() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMjg0NzA0NzAwNSwKICAicHJvZmlsZUlkIiA6ICIzOThiZGM3NWVhYzQ0ZjMzYWEyMDBiMTYyNTRmMDhlOSIsCiAgInByb2ZpbGVOYW1lIiA6ICJJa2h3YW4wNTEwIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzU3Y2FiMGMzNGQ3ZGRjZjcyZGI1NmZmMzZmMjg4M2Y1NTRjZmY3NmViNWQzYjNlMDU2MjMzODAzNmM5NzYwNDMiCiAgICB9CiAgfQp9"; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(14.5, 69, 23.5, -25.3f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 12 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "In life there are §csharks§f and there are §asheep§f.", + "I'm a §cshark§f.", + "Well, I'm a rabbit. But you get the idea." + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "I am the heir to the §6chocolate throne§f.", + "The only thing in my way? You. Please hire me, boss." + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "Heir to the §6chocolate throne§f, they say.", + "More like heir to a bunch of headaches, courtesy of my own daughter's protests." + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "Every day, it's a new challenge. If it's not the market, it's Sis with her picket signs.", + "Still, we're making the world sweeter, one chocolate bar at a time." + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "Every family has its ups and downs, but ours?", + "We've got a whole soap opera thanks to Sis." + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "Between Bro's grindset, Cousin's relaxed approach, and Granny's wisdom, we've got all the ingredients for success.", + "Now, if only Sis would see that." + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "Bro's dedication is what this company needs more of. And Cousin, well, he brings...§dcreativity§f.", + "Granny? She's the glue holding us all together." + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "§aHoppity §fwants us to 'think outside the box.' Last time I did that, we ended up with chocolate-covered grass.", + "Sold out in a week. Shows what I know." + }).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "In life there are §csharks§f and there are §asheep§f.", + "I'm a §cshark§f.", + "Well, I'm a rabbit. But you get the idea." + }).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "I am the heir to the §6chocolate throne§f.", + "The only thing in my way? You. Please hire me, boss." + }).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "Heir to the §6chocolate throne§f, they say.", + "More like heir to a bunch of headaches, courtesy of my own daughter's protests." + }).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "Every day, it's a new challenge. If it's not the market, it's Sis with her picket signs.", + "Still, we're making the world sweeter, one chocolate bar at a time." + }).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDog.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDog.java new file mode 100644 index 000000000..87dfafc3f --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitDog.java @@ -0,0 +1,219 @@ +package net.swofty.type.hub.npcs; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitDog extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Dog"; + + public NPCRabbitDog() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxNDk1OTAyNzAyNCwKICAicHJvZmlsZUlkIiA6ICJiZDNhNWRmY2ZkZjg0NDczOTViZDJiZmUwNGY0YzAzMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJwcmVja3Jhc25vIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzM1Y2E5OGJlZGUzODY1ZGQxMjA1ZTRkMDkxMDM2Y2Q5ZGMzNjc5MWI4M2VhNGUwZmY0YTk5YWQ2MWI3MWU4OTgiCiAgICB9CiAgfQp9"; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(15.5, 69.25, 13.5, 16.9f, -21.1f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 26 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + Sound barkSound = Sound.sound(Key.key("entity.wolf.ambient"), Sound.Source.NEUTRAL, 1.0f, 1.0f); + + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "In the pursuit of excellence, one must remember that the journey is as significant as the destination.", + "True success lies not in the accolades, but in the lessons learned along the way." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "Consider the possibility that limitations are merely figments of our imagination, crafted by our fears and insecurities.", + "To break free, one must first believe in the boundlessness of their own potential." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "Empathy is the silent language of the soul, spoken through actions rather than words.", + "To truly understand another, one must listen not only with ears but with the heart." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "The greatest paradox of life is that in seeking control, we often surrender it.", + "True power comes not from dominion over others, but from mastery over oneself." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "Wisdom often comes to us in whispers, through the leaves of the trees or the ripple of the waters.", + "It teaches us that every voice, no matter how small, has something of value to say." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "In every moment of decision, we stand at the crossroads of countless possibilities.", + "The paths we choose not only define our destiny but also reflect the essence of our being." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "Solitude is not the absence of company, but the moment when our soul is free to speak to us, unencumbered by the chaos of the world." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "Life, like a vast symphony, plays on the strings of our experiences, emotions, and encounters.", + "Each note, while fleeting, contributes to the eternal melody of our existence." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "Ah, Rabbit Bro, tirelessly toiling from the break of dawn, clinging to his routines with the desperation of a shipwreck survivor to a life raft.", + "One wonders if he runs from failure or merely jogs alongside it." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "And there strides Rabbit Sis, torchbearer of tumult, whose fervent protests might one day change the world, if they don't first incite her to single-handedly dismantle it, piece by bureaucratic piece." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "Ever observant Rabbit Daddy, guardian of the gold, manages finances with a paranoia that would make a conspiracy theorist blush.", + "His ledger is tighter than a drum, his brow perpetually furrowed in fiscal fear." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "Granny, with anecdotes as endless as eternity, dispenses wisdom like a vending machine stuck on dispense, whether you requested it or not.", + "Each story a subtle reminder that history repeats itself, especially at family gatherings." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-13").lines(new String[]{ + "Observe the tranquil Rabbit Cuz, philosopher of sloth, whose profound punctuality issues suggest a man deeply at peace with life's ephemeral nature, or perhaps just deeply asleep." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-14").lines(new String[]{ + "Bark!" + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-15").lines(new String[]{ + "*pants*" + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-16").lines(new String[]{ + "Woof!" + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-17").lines(new String[]{ + "In the pursuit of excellence, one must remember that the journey is as significant as the destination.", + "True success lies not in the accolades, but in the lessons learned along the way." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-18").lines(new String[]{ + "Consider the possibility that limitations are merely figments of our imagination, crafted by our fears and insecurities.", + "To break free, one must first believe in the boundlessness of their own potential." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-19").lines(new String[]{ + "Empathy is the silent language of the soul, spoken through actions rather than words.", + "To truly understand another, one must listen not only with ears but with the heart." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-20").lines(new String[]{ + "The greatest paradox of life is that in seeking control, we often surrender it.", + "True power comes not from dominion over others, but from mastery over oneself." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-21").lines(new String[]{ + "Wisdom often comes to us in whispers, through the leaves of the trees or the ripple of the waters.", + "It teaches us that every voice, no matter how small, has something of value to say." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-22").lines(new String[]{ + "In every moment of decision, we stand at the crossroads of countless possibilities.", + "The paths we choose not only define our destiny but also reflect the essence of our being." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-23").lines(new String[]{ + "Solitude is not the absence of company, but the moment when our soul is free to speak to us, unencumbered by the chaos of the world." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-24").lines(new String[]{ + "Life, like a vast symphony, plays on the strings of our experiences, emotions, and encounters.", + "Each note, while fleeting, contributes to the eternal melody of our existence." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-25").lines(new String[]{ + "Ah, Rabbit Bro, tirelessly toiling from the break of dawn, clinging to his routines with the desperation of a shipwreck survivor to a life raft.", + "One wonders if he runs from failure or merely jogs alongside it." + }).sound(barkSound).build(), + DialogueSet.builder() + .key("dialogue-26").lines(new String[]{ + "And there strides Rabbit Sis, torchbearer of tumult, whose fervent protests might one day change the world, if they don't first incite her to single-handedly dismantle it, piece by bureaucratic piece." + }).sound(barkSound).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitGranny.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitGranny.java new file mode 100644 index 000000000..6bdb9869d --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitGranny.java @@ -0,0 +1,163 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitGranny extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Granny"; + + public NPCRabbitGranny() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMjU5NDIyNDA2NCwKICAicHJvZmlsZUlkIiA6ICI2OGVmMmM5NTc5NjM0MjE4YjYwNTM5YWVlOTU3NWJiNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUaGVNdWx0aUFjb3VudCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kNmViMmQ4NWVlOGUzYWYxYzJlYzkzNGJlYjcwYTM5YzVlNzY2YjIzYmRhYjYzMjEwYmQyYWFjZDczY2JiZmM4IgogICAgfQogIH0KfQ=="; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(9.5, 69, 23.5, -85.8f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 14 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "I'm looking to come out of retirement for §done last job§f.", + "Plus, I decided I don't think I could ever give up chocolate." + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "The §6Chocolate Factory §fused to be different before §aHoppity§f arrived.", + "You never know that you're in the glory days until it's too late." + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "In my day, we made sweets from whatever was in the garden. §aHoppity§f just added cocoa.", + "Kids these days think they've invented sugar." + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "Every time §aHoppity§f talks about 'expanding the business', I remind him: 'Don't forget to expand your heart too.'", + "He's a good boy, he listens." + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "§aHoppity§f wants to automate the chocolate wrapping. I told him, 'Nothing beats the personal touch.'", + "He gave me a computerized knitting machine." + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "I've watched this family business grow from a single carrot patch to rows of cocoa trees.", + "Each bar we produce carries that legacy. Makes an old bunny proud." + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "They say you can't choose your family. But if I could, I'd choose this chocolate-crazed bunch every time.", + "From Dust Bowl to chocolate empire, we've come a long way." + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "In my days, we settled disputes over a hot cup of cocoa.", + "Maybe that's what Sis and my son need - a reminder of what binds us." + }).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "I'm looking to come out of retirement for §done last job§f.", + "Plus, I decided I don't think I could ever give up chocolate." + }).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "The §6Chocolate Factory §fused to be different before §aHoppity§f arrived.", + "You never know that you're in the glory days until it's too late." + }).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "In my day, we made sweets from whatever was in the garden. §aHoppity§f just added cocoa.", + "Kids these days think they've invented sugar." + }).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "Every time §aHoppity§f talks about 'expanding the business', I remind him: 'Don't forget to expand your heart too.'", + "He's a good boy, he listens." + }).build(), + DialogueSet.builder() + .key("dialogue-13").lines(new String[]{ + "§aHoppity§f wants to automate the chocolate wrapping. I told him, 'Nothing beats the personal touch.'", + "He gave me a computerized knitting machine." + }).build(), + DialogueSet.builder() + .key("dialogue-14").lines(new String[]{ + "I've watched this family business grow from a single carrot patch to rows of cocoa trees.", + "Each bar we produce carries that legacy. Makes an old bunny proud." + }).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitSis.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitSis.java new file mode 100644 index 000000000..aa03bd006 --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitSis.java @@ -0,0 +1,185 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitSis extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Sis"; + + public NPCRabbitSis() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxMjg0NzA5MzAxMSwKICAicHJvZmlsZUlkIiA6ICIyMWNjMzkxZmNkMjc0NzY5OTg5Y2M3M2VjYWRiNTE3YiIsCiAgInByb2ZpbGVOYW1lIiA6ICJHT1NUTFk5NyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9mZDA3NmUwZTNkNDA3MmQwZmZmZWUwYTg3YTVkNzI2ZmMzNGIyYmNlYzM4YzI2NGZiOWI2Nzg3MWE4ZWFkNjMzIgogICAgfQogIH0KfQ=="; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(18.5, 69, 24.5, 127f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 19 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "Hire me, " + player.getUsername() + "!", + "Together we can abolish the patriarchy, and ensure clean chocolate for all rabbits!" + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + "Down with Big Chocolate!" + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "Regulate! Regulate! Regulate!" + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "Chocolate is love, chocolate is life. But at what cost?", + "It's time this family faces the music and listens to the cocoa beans' side of the story!" + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "They call me a troublemaker, a rebel. I say, I'm the only one talking sense!", + "Wake up and smell the exploitation, family!" + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "A protest a day keeps the unethical practices away.", + "Dad might not see it now, but I'm doing this for the future of all chocolate bunnies." + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "They say I'm disrupting the peace, but I'm just trying to sprinkle a little truth on our chocolate-covered lies.", + "The factory needs a new recipe...for justice." + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "Bro's always talking about his 'sigma grindset'. Tried to get me to read a book on it.", + "I told him I'd start my own movement: the 'chocolate mindfulness mindset'." + }).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "Cousin might be the laziest bunny I know, but he's onto something.", + "Why protest when you can just 'be the change'? By napping, apparently." + }).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "I told §aHoppity§f we should have a line of eco-friendly chocolates.", + "He asked if green food coloring counted.", + "We're...working on it." + }).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "§aHoppity§f actually listens to my protests.", + "Well, more like he can't avoid them since I do it in the lobby. But it's a start, right?" + }).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "Hire me, " + player.getUsername() + "!", + "Together we can abolish the patriarchy, and ensure clean chocolate for all rabbits!" + }).build(), + DialogueSet.builder() + .key("dialogue-13").lines(new String[]{ + "Down with Big Chocolate!" + }).build(), + DialogueSet.builder() + .key("dialogue-14").lines(new String[]{ + "Regulate! Regulate! Regulate!" + }).build(), + DialogueSet.builder() + .key("dialogue-15").lines(new String[]{ + "Chocolate is love, chocolate is life. But at what cost?", + "It's time this family faces the music and listens to the cocoa beans' side of the story!" + }).build(), + DialogueSet.builder() + .key("dialogue-16").lines(new String[]{ + "They call me a troublemaker, a rebel. I say, I'm the only one talking sense!", + "Wake up and smell the exploitation, family!" + }).build(), + DialogueSet.builder() + .key("dialogue-17").lines(new String[]{ + "A protest a day keeps the unethical practices away.", + "Dad might not see it now, but I'm doing this for the future of all chocolate bunnies." + }).build(), + DialogueSet.builder() + .key("dialogue-18").lines(new String[]{ + "They say I'm disrupting the peace, but I'm just trying to sprinkle a little truth on our chocolate-covered lies.", + "The factory needs a new recipe...for justice." + }).build(), + DialogueSet.builder() + .key("dialogue-19").lines(new String[]{ + "Bro's always talking about his 'sigma grindset'. Tried to get me to read a book on it.", + "I told him I'd start my own movement: the 'chocolate mindfulness mindset'." + }).build() + ).toArray(DialogueSet[]::new); + } +} diff --git a/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitUncle.java b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitUncle.java new file mode 100644 index 000000000..1d3cc10a1 --- /dev/null +++ b/type.hub/src/main/java/net/swofty/type/hub/npcs/NPCRabbitUncle.java @@ -0,0 +1,216 @@ +package net.swofty.type.hub.npcs; + +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.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.stream.Stream; + +public class NPCRabbitUncle extends HypixelNPC { + private static final ChocolateFactoryRank RANK = ChocolateFactoryRank.UNEMPLOYED; + private static final String NPC_NAME = "Rabbit Uncle"; + + public NPCRabbitUncle() { + super(new HumanConfiguration() { + @Override + public boolean visible(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + return data.getEmployees().containsKey(NPC_NAME); + } + return false; + } + + @Override + public String[] holograms(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return new String[]{rank.getHologramLine(employee.getLevel()), rank.getChatName(NPC_NAME), "§e§lCLICK"}; + } + } + return new String[]{RANK.getHologramLine(), RANK.getChatName(NPC_NAME), "§e§lCLICK"}; + } + + @Override + public String signature(HypixelPlayer player) { + return ""; + } + + @Override + public String texture(HypixelPlayer player) { + return "ewogICJ0aW1lc3RhbXAiIDogMTcxNjkwOTAxMDg2NywKICAicHJvZmlsZUlkIiA6ICJmYjZkM2E5Zjk3MWY0ZTdlYmQ0MjE2Yjk0MjE5NDA3NCIsCiAgInByb2ZpbGVOYW1lIiA6ICJtYXJjaXhkZCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9hODY1MTc2NzIzYTBiOWVlMjkxNjE4MGE1NWEwNGNjY2I3NzA0YWQxZjMxZmRmM2U5ZDg5Yzc5OGY2ODAyZTZiIgogICAgfQogIH0KfQ=="; + } + + @Override + public Pos position(HypixelPlayer player) { + return new Pos(-18.5, 71, 16.5, -105.5f, 0f); + } + + @Override + public boolean looking(HypixelPlayer player) { + return true; + } + + @Override + public String chatName() { + return RANK.getChatName(NPC_NAME); + } + + @Override + public String chatName(HypixelPlayer player) { + if (player instanceof SkyBlockPlayer skyBlockPlayer) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(skyBlockPlayer); + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(NPC_NAME); + if (employee != null) { + ChocolateFactoryRank rank = ChocolateFactoryRank.fromLevel(employee.getLevel()); + return rank.getChatName(NPC_NAME); + } + } + return RANK.getChatName(NPC_NAME); + } + }); + } + + @Override + public void onClick(NPCInteractEvent e) { + if (isInDialogue(e.player())) return; + setDialogue(e.player(), "dialogue-" + (int) (Math.random() * 25 + 1)); + } + + @Override + public DialogueSet[] dialogues(HypixelPlayer player) { + return Stream.of( + DialogueSet.builder() + .key("dialogue-1").lines(new String[]{ + "Grandma always said I could be somebody if I put my mind to it.", + "She was wrong." + }).build(), + DialogueSet.builder() + .key("dialogue-2").lines(new String[]{ + player.getUsername() + "...you have to hire me. Please." + }).build(), + DialogueSet.builder() + .key("dialogue-3").lines(new String[]{ + "I should be in the Hall of Fame by now.", + "Instead, I am here. Please, hire me." + }).build(), + DialogueSet.builder() + .key("dialogue-4").lines(new String[]{ + "Back in high school, I was just one play away from that state championship.", + "If only I had zigged instead of zagged..." + }).build(), + DialogueSet.builder() + .key("dialogue-5").lines(new String[]{ + "I used to chuck that ball like nobody's business. Those were the days, eh?", + "Still got some of those moves, you know." + }).build(), + DialogueSet.builder() + .key("dialogue-6").lines(new String[]{ + "Every time I see a football, I can't help but wonder 'What if?'", + "Was so close to grabbing that championship ring." + }).build(), + DialogueSet.builder() + .key("dialogue-7").lines(new String[]{ + "Sometimes, late at night, I replay that final drive in my head.", + "I could have been a legend, you know?" + }).build(), + DialogueSet.builder() + .key("dialogue-8").lines(new String[]{ + "Man." + }).build(), + DialogueSet.builder() + .key("dialogue-9").lines(new String[]{ + "I've got the entire high school trophy case memorized.", + "Sometimes, I give tours. You know, just to keep the legacy going." + }).build(), + DialogueSet.builder() + .key("dialogue-10").lines(new String[]{ + "Cuz might not have the typical athlete's discipline, but he's clutch.", + "Just like my old teammate who didn't show up to every practice, but could score tuddies when it mattered most." + }).build(), + DialogueSet.builder() + .key("dialogue-11").lines(new String[]{ + "§dHoppity§f's like the star quarterback of this chocolate game. Always looking for that next big play.", + "He's got that championship mindset, just like I had." + }).build(), + DialogueSet.builder() + .key("dialogue-12").lines(new String[]{ + "Sis has that fire, like the team captains back in my day.", + "She doesn't just play - she changes the game.", + "Always rallying the troops for her cause, much like a good quarterback does in the fourth quarter." + }).build(), + DialogueSet.builder() + .key("dialogue-13").lines(new String[]{ + "Sure, Rabbit Bro's up at dawn doing push-ups and planning his day.", + "I used to be like him, then I tore my ACL.", + "My knee has never been the same since, man." + }).build(), + DialogueSet.builder() + .key("dialogue-14").lines(new String[]{ + "Cuz showing up to work is like a trick play.", + "You never see it coming, and when it happens you can't help but wonder if it was a fluke." + }).build(), + DialogueSet.builder() + .key("dialogue-15").lines(new String[]{ + "Grandma always said I could be somebody if I put my mind to it.", + "She was wrong." + }).build(), + DialogueSet.builder() + .key("dialogue-16").lines(new String[]{ + player.getUsername() + "...you have to hire me. Please." + }).build(), + DialogueSet.builder() + .key("dialogue-17").lines(new String[]{ + "I should be in the Hall of Fame by now.", + "Instead, I am here. Please, hire me." + }).build(), + DialogueSet.builder() + .key("dialogue-18").lines(new String[]{ + "Back in high school, I was just one play away from that state championship.", + "If only I had zigged instead of zagged..." + }).build(), + DialogueSet.builder() + .key("dialogue-19").lines(new String[]{ + "I used to chuck that ball like nobody's business. Those were the days, eh?", + "Still got some of those moves, you know." + }).build(), + DialogueSet.builder() + .key("dialogue-20").lines(new String[]{ + "Every time I see a football, I can't help but wonder 'What if?'", + "Was so close to grabbing that championship ring." + }).build(), + DialogueSet.builder() + .key("dialogue-21").lines(new String[]{ + "Sometimes, late at night, I replay that final drive in my head.", + "I could have been a legend, you know?" + }).build(), + DialogueSet.builder() + .key("dialogue-22").lines(new String[]{ + "Man." + }).build(), + DialogueSet.builder() + .key("dialogue-23").lines(new String[]{ + "I've got the entire high school trophy case memorized.", + "Sometimes, I give tours. You know, just to keep the legacy going." + }).build(), + DialogueSet.builder() + .key("dialogue-24").lines(new String[]{ + "Cuz might not have the typical athlete's discipline, but he's clutch.", + "Just like my old teammate who didn't show up to every practice, but could score tuddies when it mattered most." + }).build(), + DialogueSet.builder() + .key("dialogue-25").lines(new String[]{ + "§dHoppity§f's like the star quarterback of this chocolate game. Always looking for that next big play.", + "He's got that championship mindset, just like I had." + }).build() + ).toArray(DialogueSet[]::new); + } +} 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 48ec42f11..065510a33 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 @@ -80,6 +80,7 @@ import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; import net.swofty.type.skyblockgeneric.user.SkyBlockScoreboard; import net.swofty.type.skyblockgeneric.user.StashReminder; + import net.swofty.type.generic.user.categories.CustomGroups; import net.swofty.type.skyblockgeneric.user.fairysouls.FairySoul; import net.swofty.type.skyblockgeneric.user.fairysouls.FairySoulZone; @@ -326,6 +327,7 @@ public void initialize(MinecraftServer server) { // Start repeaters SkyBlockScoreboard.start(); StashReminder.start(MinecraftServer.getSchedulerManager()); + PlayerHolograms.updateAll(MinecraftServer.getSchedulerManager()); /** diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateFactoryHelper.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateFactoryHelper.java new file mode 100644 index 000000000..f9b93304d --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateFactoryHelper.java @@ -0,0 +1,487 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.UtilityClass; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.function.Consumer; + +/** + * Helper class for Chocolate Factory operations. + * Provides utility methods for production calculation, formatting, and player interactions. + */ +@UtilityClass +public class ChocolateFactoryHelper { + private static final int SECONDS_PER_HOUR = 3600; + + private static final int HAND_BAKED_MAX_INTERNAL_LEVEL = 9; + private static final long HAND_BAKED_COST_STEP = 500L; + + private static final long RABBIT_BARN_BASE_COST = 5000L; + private static final double RABBIT_BARN_COST_GROWTH = 1.05; + + private static final String RABBIT_BRO_NAME = "Rabbit Bro"; + private static final int RABBIT_BRO_SPECIAL_MAX_LEVEL = 10; + private static final int RABBIT_BRO_BASE_COST = 30; + private static final int RABBIT_BRO_CF_SCALING = 20; + private static final int EMPLOYEE_BASE_COST = 216; + private static final double EMPLOYEE_CF_SCALING = 144.0; + private static final double EMPLOYEE_COST_GROWTH = 1.05; + + private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.US); + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.0"); + + /** + * Gets the chocolate factory data for a player + */ + public static DatapointChocolateFactory.ChocolateFactoryData getData(SkyBlockPlayer player) { + return player.getChocolateFactoryData(); + } + + /** + * Gets the chocolate factory datapoint for a player + */ + public static DatapointChocolateFactory getDatapoint(SkyBlockPlayer player) { + return player.getChocolateFactoryDatapoint(); + } + + /** + * Updates chocolate production for the player based on time elapsed + */ + public static void updateProduction(SkyBlockPlayer player) { + mutateData(player, DatapointChocolateFactory.ChocolateFactoryData::updateChocolateFromProduction); + } + + /** + * Handles a click on the chocolate cookie + */ + public static void handleClick(SkyBlockPlayer player) { + mutateData(player, DatapointChocolateFactory.ChocolateFactoryData::click); + } + + /** + * Formats chocolate amount for display with commas (e.g., 1,000 not 1k) + */ + public static String formatChocolate(long amount) { + return NUMBER_FORMAT.format(amount); + } + + /** + * Formats production per second for display + */ + public static String formatProductionPerSecond(double production) { + return DECIMAL_FORMAT.format(production) + "/s"; + } + + /** + * Formats production per hour for display + */ + public static String formatProductionPerHour(double productionPerSecond) { + double perHour = productionPerSecond * SECONDS_PER_HOUR; + return formatChocolate((long) perHour) + "/h"; + } + + /** + * Gets the upgrade cost for Hand-Baked Chocolate + * Level 1→2: 500, Level 2→3: 1000, etc. + * Internal level 0 = display level 1, so cost = 500 * (internalLevel + 1) + * Max internal level is 9 (display level 10) + */ + public static long getHandBakedChocolateCost(int currentLevel) { + if (currentLevel >= HAND_BAKED_MAX_INTERNAL_LEVEL) return 0; // Already maxed at level 10 + return HAND_BAKED_COST_STEP * (currentLevel + 1); + } + + /** + * Gets the upgrade cost for Rabbit Barn + * Level 1 is unlocked by default (free) + * Level 2 costs 5,000 Chocolate + * Subsequent levels: floor(5000 × 1.05^(L-2)) where L is target level + */ + public static long getRabbitBarnCost(int currentLevel) { + // Level 1 is free (unlocked by default) + if (currentLevel == 0) { + return 0; + } + // Cost formula: 5000 × 1.05^(currentLevel-1), rounded + return Math.round(RABBIT_BARN_BASE_COST * Math.pow(RABBIT_BARN_COST_GROWTH, currentLevel - 1)); + } + + /** + * Gets the upgrade cost for Rabbit Shrine + * Max level is 20, providing 40% extra chance for higher rarity rabbits + */ + public static long getRabbitShrineCost(int currentLevel) { + // Cost to upgrade TO level (currentLevel + 1) + return switch (currentLevel) { + case 0 -> 10_000_000L; // To level 1 + case 1 -> 20_000_000L; // To level 2 + case 2 -> 30_000_000L; // To level 3 + case 3 -> 40_000_000L; // To level 4 + case 4 -> 60_000_000L; // To level 5 + case 5 -> 80_000_000L; // To level 6 + case 6 -> 100_000_000L; // To level 7 + case 7 -> 120_000_000L; // To level 8 + case 8 -> 150_000_000L; // To level 9 + case 9 -> 200_000_000L; // To level 10 + case 10 -> 250_000_000L; // To level 11 + case 11 -> 300_000_000L; // To level 12 + case 12 -> 350_000_000L; // To level 13 + case 13 -> 400_000_000L; // To level 14 + case 14 -> 450_000_000L; // To level 15 + case 15 -> 500_000_000L; // To level 16 + case 16 -> 550_000_000L; // To level 17 + case 17 -> 600_000_000L; // To level 18 + case 18 -> 650_000_000L; // To level 19 + case 19 -> 700_000_000L; // To level 20 + default -> 0L; // Already maxed + }; + } + + /** + * Gets the upgrade cost for Time Tower based on prestige level + * Level 1 is unlocked by default (free) + */ + public static long getTimeTowerCost(int currentLevel, int prestigeLevel) { + // Level 1 is free (unlocked by default) + if (currentLevel == 0) { + return 0; + } + + // Base cost depends on prestige (Factory II = prestige 1, Factory III = prestige 2, etc.) + long baseCost = switch (prestigeLevel) { + case 1 -> 5_500_000L; // Factory II + case 2 -> 6_000_000L; // Factory III + case 3 -> 6_500_000L; // Factory IV + case 4 -> 7_000_000L; // Factory V + default -> 7_500_000L; // Factory VI+ + }; + + // Multiplier for each level (upgrading TO level currentLevel+1) + int multiplier = switch (currentLevel) { + case 1 -> 1; // To level 2 + case 2 -> 2; // To level 3 + case 3 -> 3; // To level 4 + case 4 -> 4; // To level 5 + case 5 -> 6; // To level 6 + case 6 -> 8; // To level 7 + case 7 -> 10; // To level 8 + case 8 -> 12; // To level 9 + case 9 -> 14; // To level 10 + case 10 -> 16; // To level 11 + case 11 -> 20; // To level 12 + case 12 -> 24; // To level 13 + case 13 -> 30; // To level 14 + case 14 -> 40; // To level 15 + default -> 40; + }; + + return baseCost * multiplier; + } + + /** + * Gets the upgrade cost for Time Tower (legacy, uses minimum prestige) + */ + public static long getTimeTowerCost(int currentLevel) { + return getTimeTowerCost(currentLevel, 1); + } + + /** + * Gets the upgrade cost for Coach Jackrabbit + * Max level is 20, providing +0.2x CpS multiplier + */ + public static long getCoachJackrabbitCost(int currentLevel) { + // Cost to upgrade TO level (currentLevel + 1) + return switch (currentLevel) { + case 0 -> 2_200_000L; // To level 1 + case 1 -> 3_900_000L; // To level 2 + case 2 -> 5_300_000L; // To level 3 + case 3 -> 7_200_000L; // To level 4 + case 4 -> 9_700_000L; // To level 5 + case 5 -> 13_000_000L; // To level 6 + case 6 -> 18_000_000L; // To level 7 + case 7 -> 24_000_000L; // To level 8 + case 8 -> 32_000_000L; // To level 9 + case 9 -> 43_000_000L; // To level 10 + case 10 -> 59_000_000L; // To level 11 + case 11 -> 79_000_000L; // To level 12 + case 12 -> 110_000_000L; // To level 13 + case 13 -> 140_000_000L; // To level 14 + case 14 -> 190_000_000L; // To level 15 + case 15 -> 260_000_000L; // To level 16 + case 16 -> 350_000_000L; // To level 17 + case 17 -> 480_000_000L; // To level 18 + case 18 -> 650_000_000L; // To level 19 + case 19 -> 870_000_000L; // To level 20 + default -> 0L; // Already maxed + }; + } + + /** + * Gets the employee index (1-7) for cost calculation + */ + public static int getEmployeeIndex(String employeeName) { + return getEmployeeType(employeeName).getIndex(); + } + + /** + * Gets the cost to hire/upgrade an employee at a specific level + * For Rabbit Bro levels 1-10: (30 + 20 × CF) × multiplier + * For all other cases: base_cost × 1.05^L where base_cost = (216 + 144 × CF) × i² + */ + public static long getEmployeeCost(String employeeName, int targetLevel, int chocolateFactoryLevel) { + EmployeeType employeeType = getEmployeeType(employeeName); + int employeeIndex = employeeType.getIndex(); + int cf = chocolateFactoryLevel; // CF level 1-6 + + // Rabbit Bro's first 10 levels use special formula + if (RABBIT_BRO_NAME.equals(employeeName) && targetLevel <= RABBIT_BRO_SPECIAL_MAX_LEVEL) { + double multiplier = getRabbitBroMultiplier(targetLevel); + return (long) ((RABBIT_BRO_BASE_COST + RABBIT_BRO_CF_SCALING * cf) * multiplier); + } + + // All other cases: base_cost × 1.05^L + double baseCost = (EMPLOYEE_BASE_COST + EMPLOYEE_CF_SCALING * cf) * (employeeIndex * employeeIndex); + return (long) (baseCost * Math.pow(EMPLOYEE_COST_GROWTH, targetLevel)); + } + + /** + * Gets the cost to hire/upgrade an employee (uses player's CF level) + */ + public static long getEmployeeCost(SkyBlockPlayer player, String employeeName, int targetLevel) { + int cfLevel = getData(player).getPrestigeLevel() + 1; // Prestige 0 = CF1, etc. + return getEmployeeCost(employeeName, targetLevel, cfLevel); + } + + /** + * Legacy method - assumes CF level 1 + */ + public static long getEmployeeCost(String employeeName, int targetLevel) { + return getEmployeeCost(employeeName, targetLevel, 1); + } + + /** + * Gets the multiplier for Rabbit Bro's first 10 levels + */ + private static double getRabbitBroMultiplier(int level) { + return switch (level) { + case 1 -> 1; + case 2 -> 2; + case 3 -> 4; + case 4 -> 6; + case 5 -> 8; + case 6 -> 9; + case 7 -> 9.5; + case 8 -> 10; + case 9 -> 10.5; + case 10 -> 11; + default -> 11; + }; + } + + /** + * Gets the base production per level for an employee type + * This is the chocolate per second gained per employee level + */ + public static double getEmployeeBaseProduction(String employeeName) { + return getEmployeeType(employeeName).getBaseProductionPerLevel(); + } + + /** + * Gets the employee that must be at level 20 to unlock the given employee + * Returns null if no prerequisite (Rabbit Bro) + */ + public static String getEmployeePrerequisite(String employeeName) { + return getEmployeeType(employeeName).getPrerequisiteEmployee(); + } + + /** + * Checks if an employee is unlocked for a player + */ + public static boolean isEmployeeUnlocked(SkyBlockPlayer player, String employeeName) { + String prerequisite = getEmployeePrerequisite(employeeName); + if (prerequisite == null) return true; // No prerequisite + + DatapointChocolateFactory.ChocolateFactoryData data = getData(player); + DatapointChocolateFactory.EmployeeData prereqEmployee = data.getEmployees().get(prerequisite); + return prereqEmployee != null && prereqEmployee.getLevel() >= 20; + } + + /** + * Tries to purchase an upgrade + * @return true if purchase was successful + */ + public static boolean purchaseUpgrade(SkyBlockPlayer player, UpgradeType type) { + DatapointChocolateFactory datapoint = getDatapoint(player); + DatapointChocolateFactory.ChocolateFactoryData data = datapoint.getValue(); + + long cost = switch (type) { + case HAND_BAKED_CHOCOLATE -> getHandBakedChocolateCost(data.getHandBakedChocolateLevel()); + case RABBIT_BARN -> getRabbitBarnCost(data.getRabbitBarnLevel()); + case RABBIT_SHRINE -> getRabbitShrineCost(data.getRabbitShrineLevel()); + case TIME_TOWER -> getTimeTowerCost(data.getTimeTowerLevel(), data.getPrestigeLevel()); + case COACH_JACKRABBIT -> getCoachJackrabbitCost(data.getCoachJackrabbitLevel()); + }; + + if (data.removeChocolate(cost)) { + switch (type) { + case HAND_BAKED_CHOCOLATE -> data.setHandBakedChocolateLevel(data.getHandBakedChocolateLevel() + 1); + case RABBIT_BARN -> data.setRabbitBarnLevel(data.getRabbitBarnLevel() + 1); + case RABBIT_SHRINE -> data.setRabbitShrineLevel(data.getRabbitShrineLevel() + 1); + case TIME_TOWER -> data.setTimeTowerLevel(data.getTimeTowerLevel() + 1); + case COACH_JACKRABBIT -> data.setCoachJackrabbitLevel(data.getCoachJackrabbitLevel() + 1); + } + datapoint.setValue(data); + return true; + } + return false; + } + + /** + * Tries to hire or upgrade an employee + * @return true if purchase was successful + */ + public static boolean hireOrUpgradeEmployee(SkyBlockPlayer player, String employeeName) { + DatapointChocolateFactory datapoint = getDatapoint(player); + DatapointChocolateFactory.ChocolateFactoryData data = datapoint.getValue(); + + DatapointChocolateFactory.EmployeeData existing = data.getEmployees().get(employeeName); + int targetLevel = existing != null ? existing.getLevel() + 1 : 1; + + // Check if employee is unlocked (prerequisite employee at level 20) + if (!isEmployeeUnlocked(player, employeeName)) { + return false; + } + + long cost = getEmployeeCost(player, employeeName, targetLevel); + + if (data.removeChocolate(cost)) { + double baseProduction = getEmployeeBaseProduction(employeeName); + data.setEmployee(employeeName, targetLevel, baseProduction); + datapoint.setValue(data); + return true; + } + return false; + } + + /** + * Activates the Time Tower for the player + * @return true if activation was successful + */ + public static boolean activateTimeTower(SkyBlockPlayer player) { + DatapointChocolateFactory datapoint = getDatapoint(player); + DatapointChocolateFactory.ChocolateFactoryData data = datapoint.getValue(); + + if (data.activateTimeTower()) { + datapoint.setValue(data); + return true; + } + return false; + } + + /** + * Adds a Time Tower charge to the player + */ + public static void addTimeTowerCharge(SkyBlockPlayer player) { + mutateData(player, data -> data.setTimeTowerCharges(data.getTimeTowerCharges() + 1)); + } + + /** + * Gets the prestige rank name based on level + */ + public static String getPrestigeRankName(int level) { + return Prestige.fromLevel(level).getName(); + } + + /** + * Gets the prestige rank color based on level + */ + public static String getPrestigeRankColor(int level) { + return Prestige.fromLevel(level).getColor(); + } + + /** + * Gets the Prestige enum based on level + */ + public static Prestige getPrestige(int level) { + return Prestige.fromLevel(level); + } + + private static void mutateData(SkyBlockPlayer player, Consumer mutator) { + DatapointChocolateFactory datapoint = getDatapoint(player); + DatapointChocolateFactory.ChocolateFactoryData data = datapoint.getValue(); + mutator.accept(data); + datapoint.setValue(data); + } + + private static EmployeeType getEmployeeType(String employeeName) { + return switch (employeeName) { + case "Rabbit Bro" -> EmployeeType.RABBIT_BRO; + case "Rabbit Cousin" -> EmployeeType.RABBIT_COUSIN; + case "Rabbit Sis" -> EmployeeType.RABBIT_SIS; + case "Rabbit Daddy" -> EmployeeType.RABBIT_DADDY; + case "Rabbit Granny" -> EmployeeType.RABBIT_GRANNY; + case "Rabbit Uncle" -> EmployeeType.RABBIT_UNCLE; + case "Rabbit Dog" -> EmployeeType.RABBIT_DOG; + default -> EmployeeType.RABBIT_BRO; + }; + } + + @Getter + @AllArgsConstructor + private enum EmployeeType { + RABBIT_BRO(1, 1.0, null), + RABBIT_COUSIN(2, 2.0, RABBIT_BRO_NAME), + RABBIT_SIS(3, 3.0, "Rabbit Cousin"), + RABBIT_DADDY(4, 4.0, "Rabbit Sis"), + RABBIT_GRANNY(5, 5.0, "Rabbit Daddy"), + RABBIT_UNCLE(6, 6.0, "Rabbit Granny"), + RABBIT_DOG(7, 7.0, "Rabbit Uncle"); + + private final int index; + private final double baseProductionPerLevel; + private final String prerequisiteEmployee; + } + + @Getter + @AllArgsConstructor + public enum Prestige { + NEWCOMER(0, "Newcomer", "§7"), + APPRENTICE(1, "Apprentice", "§a"), + WORKER(2, "Worker", "§9"), + JOURNEYMAN(3, "Journeyman", "§5"), + EXPERT(4, "Expert", "§6"), + MASTER(5, "Master", "§d"), + GRANDMASTER(6, "Grandmaster", "§b"); + + private final int level; + private final String name; + private final String color; + + public String getFormattedName() { + return color + name; + } + + public static Prestige fromLevel(int level) { + for (Prestige prestige : values()) { + if (prestige.level == level) { + return prestige; + } + } + return NEWCOMER; + } + } + + public enum UpgradeType { + HAND_BAKED_CHOCOLATE, + RABBIT_BARN, + RABBIT_SHRINE, + TIME_TOWER, + COACH_JACKRABBIT + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateMilestone.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateMilestone.java new file mode 100644 index 000000000..e4180b0ba --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateMilestone.java @@ -0,0 +1,115 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents chocolate factory milestones. + * Each milestone unlocks a special rabbit when reaching a certain all-time chocolate threshold. + */ +@Getter +@AllArgsConstructor +public enum ChocolateMilestone { + // Common milestones (White) + MILESTONE_1(1, 1_000L, "Almond Amaretto Rabbit", "f", 1, 0.002, + "b1b93d4e792bbf1522915b4c5f0444659fa3614c45a2b2922e61a888e61c714e", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_2(2, 10_000L, "Buttercream Blossom Rabbit", "f", 1, 0.002, + "137f6bd5e7c4e8fb22c88eb09537079bd3d4c144cf5a376ce6f2030110bd1680", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_3(3, 100_000L, "Cocoa Comet Rabbit", "f", 1, 0.002, + "6b38fa0a62113539b18fc31d1e04be34d47b7d500ef790b53f83d81c479ed29d", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_4(4, 1_000_000L, "Dulce Drizzle Rabbit", "f", 1, 0.002, + "e9eb72f7ed9e8c37a4c9ed9e8b9afc77f6ca4e01f2a76ebf9a8e8f7b2c3d4e5f", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_5(5, 2_500_000L, "Espresso Eclair Rabbit", "f", 1, 0.002, + "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", Material.WHITE_STAINED_GLASS_PANE), + + // Uncommon milestones (Lime/Green) + MILESTONE_6(6, 5_000_000L, "Fudge Fountain Rabbit", "a", 2, 0.003, + "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_7(7, 10_000_000L, "Ginger Glaze Rabbit", "a", 2, 0.003, + "d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_8(8, 25_000_000L, "Honey Hazelnut Rabbit", "a", 2, 0.003, + "e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_9(9, 50_000_000L, "Icing Ivy Rabbit", "a", 2, 0.003, + "f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_10(10, 100_000_000L, "Jasmine Jello Rabbit", "a", 2, 0.003, + "a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3", Material.LIME_STAINED_GLASS_PANE), + + // Rare milestones (Blue) + MILESTONE_11(11, 250_000_000L, "Kiwi Kiss Rabbit", "9", 4, 0.004, + "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_12(12, 500_000_000L, "Lavender Lemon Rabbit", "9", 4, 0.004, + "c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_13(13, 1_000_000_000L, "Maple Mirage Rabbit", "9", 4, 0.004, + "d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_14(14, 2_500_000_000L, "Nougat Nebula Rabbit", "9", 4, 0.004, + "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_15(15, 5_000_000_000L, "Orange Obsidian Rabbit", "9", 4, 0.004, + "f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3", Material.BLUE_STAINED_GLASS_PANE), + + // Epic milestones (Purple) + MILESTONE_16(16, 10_000_000_000L, "Peppermint Pearl Rabbit", "5", 10, 0.005, + "a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_17(17, 25_000_000_000L, "Quince Quark Rabbit", "5", 10, 0.005, + "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_18(18, 50_000_000_000L, "Raspberry Ripple Rabbit", "5", 10, 0.005, + "c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_19(19, 100_000_000_000L, "Saffron Swirl Rabbit", "5", 10, 0.005, + "d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_20(20, 150_000_000_000L, "Toffee Tulip Rabbit", "5", 10, 0.005, + "e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4", Material.PURPLE_STAINED_GLASS_PANE), + + // Legendary milestones (Orange) - These only give multiplier, no flat chocolate + MILESTONE_21(21, 200_000_000_000L, "Ube Unicorn Rabbit", "6", 0, 0.02, + "f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_22(22, 300_000_000_000L, "Vanilla Vortex Rabbit", "6", 0, 0.02, + "a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_23(23, 400_000_000_000L, "Walnut Whirl Rabbit", "6", 0, 0.02, + "b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_24(24, 500_000_000_000L, "Xocolatl Xenon Rabbit", "6", 0, 0.02, + "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5", Material.ORANGE_STAINED_GLASS_PANE); + + private final int number; + private final long requiredChocolate; + private final String rabbitName; + private final String colorCode; + private final int chocolateBonus; + private final double multiplierBonus; + private final String textureId; + private final Material glassPaneMaterial; + + private static final Map MILESTONES_BY_NUMBER = createMilestonesByNumber(); + + public String getRomanNumeral() { + return StringUtility.getAsRomanNumeral(number); + } + + public String getFormattedRequirement() { + return ChocolateFactoryHelper.formatChocolate(requiredChocolate); + } + + public boolean isUnlocked(long allTimeChocolate) { + return allTimeChocolate >= requiredChocolate; + } + + public double getProgress(long allTimeChocolate) { + if (allTimeChocolate >= requiredChocolate) return 100.0; + return (allTimeChocolate / (double) requiredChocolate) * 100.0; + } + + public static ChocolateMilestone fromNumber(int number) { + return MILESTONES_BY_NUMBER.get(number); + } + + private static Map createMilestonesByNumber() { + Map milestonesByNumber = new HashMap<>(); + for (ChocolateMilestone milestone : values()) { + milestonesByNumber.put(milestone.number, milestone); + } + return milestonesByNumber; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateRabbit.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateRabbit.java new file mode 100644 index 000000000..7381bc7a5 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateRabbit.java @@ -0,0 +1,639 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Represents all chocolate rabbits that can be collected in Hoppity's Collection. + * Total: 513 rabbits across all rarities. + */ +@Getter +@AllArgsConstructor +public enum ChocolateRabbit { + // ==================== COMMON RABBITS (223) ==================== + AARON("Aaron", Rarity.COMMON, null, null, null), + ABLE("Able", Rarity.COMMON, null, null, null), + ACKER("Acker", Rarity.COMMON, null, null, null), + ALBUS("Albus", Rarity.COMMON, null, "Crimson Isle", null), + ALFIE("Alfie", Rarity.COMMON, null, null, null), + ALICE("Alice", Rarity.COMMON, null, null, null), + ALMOND_AMARETTO("Almond Amaretto", Rarity.COMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + ALPHA("Alpha", Rarity.COMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + ANGUS("Angus", Rarity.COMMON, null, null, null), + ANNABELLE("Annabelle", Rarity.COMMON, null, null, null), + ARCHIE("Archie", Rarity.COMMON, null, null, null), + ARNIE("Arnie", Rarity.COMMON, null, null, null), + AUDI("Audi", Rarity.COMMON, null, null, null), + AUGUSTUS("Augustus", Rarity.COMMON, null, null, null), + BABY("Baby", Rarity.COMMON, null, null, null), + BADGER("Badger", Rarity.COMMON, null, null, null), + BAGEL("Bagel", Rarity.COMMON, null, null, null), + BALDWIN("Baldwin", Rarity.COMMON, null, null, null), + BALOO("Baloo", Rarity.COMMON, null, null, null), + BARNEY("Barney", Rarity.COMMON, null, null, null), + BARTHOLOMEW("Bartholomew", Rarity.COMMON, null, null, null), + BASKET("Basket", Rarity.COMMON, null, null, null), + BAXTER("Baxter", Rarity.COMMON, null, null, null), + BAYOU("Bayou", Rarity.COMMON, null, "Backwater Bayou", "Find §a15 §7unique egg locations in the §2⏣ Backwater Bayou§7."), + BEATRICE("Beatrice", Rarity.COMMON, null, null, null), + BERTHA("Bertha", Rarity.COMMON, null, null, null), + BIBSY("Bibsy", Rarity.COMMON, null, null, null), + BILLY("Billy", Rarity.COMMON, null, null, null), + BINDI("Bindi", Rarity.COMMON, null, null, null), + BINKY("Binky", Rarity.COMMON, null, null, null), + BLAKE("Blake", Rarity.COMMON, null, null, null), + BOB("Bob", Rarity.COMMON, null, null, null), + BRAMBLE("Bramble", Rarity.COMMON, null, null, null), + BREEZE("Breeze", Rarity.COMMON, null, null, null), + BRIAN("Brian", Rarity.COMMON, null, null, null), + BRIE("Brie", Rarity.COMMON, null, null, null), + BRONSON("Bronson", Rarity.COMMON, null, null, null), + BROOKS("Brooks", Rarity.COMMON, null, null, null), + BRUCE("Bruce", Rarity.COMMON, null, null, null), + BRUNO("Bruno", Rarity.COMMON, null, null, null), + BUD("Bud", Rarity.COMMON, null, null, null), + BUGSTER("Bugster", Rarity.COMMON, null, null, null), + BUGSY("Bugsy", Rarity.COMMON, null, null, null), + BUTTERCREAM_BLOSSOM("Buttercream Blossom", Rarity.COMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + CADET("Cadet", Rarity.COMMON, null, null, null), + CALLIE("Callie", Rarity.COMMON, null, null, null), + CARROT("Carrot", Rarity.COMMON, null, null, "Kill a §cRabbit Sprite Carrot King§7."), + CAVE("Cave", Rarity.COMMON, null, "Deep Caverns", "Find §a15 §7unique egg locations in the §b⏣ Deep Caverns§7."), + CHASE("Chase", Rarity.COMMON, null, null, null), + CHESTER("Chester", Rarity.COMMON, null, null, null), + CHIP("Chip", Rarity.COMMON, null, null, null), + CHOMPER("Chomper", Rarity.COMMON, null, null, null), + CHOMPSKY("Chompsky", Rarity.COMMON, null, null, null), + CLAUDE("Claude", Rarity.COMMON, null, null, null), + COCOA_COMET("Cocoa Comet", Rarity.COMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + COLLIN("Collin", Rarity.COMMON, null, null, null), + COPPER("Copper", Rarity.COMMON, null, null, null), + COTTONTAIL("Cottontail", Rarity.COMMON, null, null, null), + CRICKET("Cricket", Rarity.COMMON, null, null, null), + CRUSH("Crush", Rarity.COMMON, null, null, "Find §a100 §7duplicate rabbits."), + CUDDLES("Cuddles", Rarity.COMMON, null, null, null), + CUPCAKE("Cupcake", Rarity.COMMON, null, null, null), + DELBOY("Delboy", Rarity.COMMON, null, null, null), + DELILAH("Delilah", Rarity.COMMON, null, null, null), + DEMI("Demi", Rarity.COMMON, null, null, null), + DIGGER("Digger", Rarity.COMMON, null, null, null), + DUCHESS("Duchess", Rarity.COMMON, null, null, null), + DULCE_DRIZZLE("Dulce Drizzle", Rarity.COMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + DUSTY("Dusty", Rarity.COMMON, null, null, null), + ELLIE("Ellie", Rarity.COMMON, null, null, null), + EMBER("Ember", Rarity.COMMON, null, "Crimson Isle", "Find §a15 §7unique egg locations in the §c⏣ Crimson Isle§7."), + EMERSON("Emerson", Rarity.COMMON, null, null, null), + EMMA("Emma", Rarity.COMMON, null, "Deep Caverns", null), + ESPRESSO_ECLAIR("Espresso Eclair", Rarity.COMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + ETA("Eta", Rarity.COMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + FERGIE("Fergie", Rarity.COMMON, null, null, null), + FIEVEL("Fievel", Rarity.COMMON, null, null, null), + FLUFFY("Fluffy", Rarity.COMMON, null, null, null), + FRANCINE("Francine", Rarity.COMMON, null, null, null), + FRANK("Frank", Rarity.COMMON, null, null, null), + FRANKIE("Frankie", Rarity.COMMON, null, null, null), + FUDGE("Fudge", Rarity.COMMON, null, null, null), + FUZZY("Fuzzy", Rarity.COMMON, null, null, null), + GEORGE("George", Rarity.COMMON, null, null, null), + GINGER("Ginger", Rarity.COMMON, null, null, null), + GINNY("Ginny", Rarity.COMMON, null, null, null), + GIZMO("Gizmo", Rarity.COMMON, null, null, null), + GLORIA("Gloria", Rarity.COMMON, null, null, null), + GOUDA("Gouda", Rarity.COMMON, null, null, null), + GRACIE("Gracie", Rarity.COMMON, null, null, null), + GUINNESS("Guinness", Rarity.COMMON, null, null, null), + GUNTHER("Gunther", Rarity.COMMON, null, null, null), + HADLEY("Hadley", Rarity.COMMON, null, null, null), + HARLAND("Harland", Rarity.COMMON, null, "Spider's Den", null), + HARLEY("Harley", Rarity.COMMON, null, null, null), + HEFNER("Hefner", Rarity.COMMON, null, null, null), + HEIDIE("Heidie", Rarity.COMMON, null, null, null), + HERBIE("Herbie", Rarity.COMMON, null, null, null), + HERSHEY("Hershey", Rarity.COMMON, null, null, null), + HONDO("Hondo", Rarity.COMMON, null, null, null), + HOPPER("Hopper", Rarity.COMMON, null, null, null), + HUBBY("Hubby", Rarity.COMMON, null, "Hub", "Find §a15 §7unique egg locations in the §bHub§7."), + HUCK("Huck", Rarity.COMMON, null, null, null), + HUGO("Hugo", Rarity.COMMON, null, null, null), + HUMPHREY("Humphrey", Rarity.COMMON, null, null, null), + HUNTER("Hunter", Rarity.COMMON, null, null, null), + IGGY("Iggy", Rarity.COMMON, null, null, null), + INDIE("Indie", Rarity.COMMON, null, null, null), + JACQUELINE("Jacqueline", Rarity.COMMON, null, "The Park", null), + JADE("Jade", Rarity.COMMON, null, "Crystal Hollows", "Find §a15 §7unique egg locations in the §5⏣ Crystal Hollows§7."), + JAKE("Jake", Rarity.COMMON, null, null, null), + JAMES("James", Rarity.COMMON, null, null, null), + JAMMER("Jammer", Rarity.COMMON, null, null, null), + JASMINE("Jasmine", Rarity.COMMON, null, null, null), + JAZMIN("Jazmin", Rarity.COMMON, null, null, null), + JEFFERY("Jeffery", Rarity.COMMON, null, null, null), + JERRY("Jerry", Rarity.COMMON, null, null, "Kill a §6Villager Sprite Golden Jerry§7."), + JOEY("Joey", Rarity.COMMON, null, null, null), + JONAH("Jonah", Rarity.COMMON, null, null, null), + JOSEPHINE("Josephine", Rarity.COMMON, null, null, null), + KRONK("Kronk", Rarity.COMMON, null, "Crystal Hollows", null), + LAZY("Lazy", Rarity.COMMON, null, "Gold Mine", null), + LENNY("Lenny", Rarity.COMMON, null, null, null), + LILY("Lily", Rarity.COMMON, null, null, null), + LONE_RANGER("Lone Ranger", Rarity.COMMON, null, null, null), + LOTTE("Lotte", Rarity.COMMON, null, null, null), + LOUIE("Louie", Rarity.COMMON, null, null, null), + MANDY("Mandy", Rarity.COMMON, null, null, null), + MARLOW("Marlow", Rarity.COMMON, null, null, null), + MARSHALL("Marshall", Rarity.COMMON, null, "Galatea", null), + MAUI("Maui", Rarity.COMMON, null, null, null), + MAX("Max", Rarity.COMMON, null, null, null), + MICKEY("Mickey", Rarity.COMMON, null, null, null), + MILES("Miles", Rarity.COMMON, null, null, null), + MILLY("Milly", Rarity.COMMON, null, null, null), + MIMI("Mimi", Rarity.COMMON, null, "Dwarven Mines", "Find §a15 §7unique egg locations in the §2⏣ Dwarven Mines§7."), + MITE("Mite", Rarity.COMMON, null, "The End", null), + MOCHI("Mochi", Rarity.COMMON, null, null, null), + MOLLY("Molly", Rarity.COMMON, null, null, null), + MONA("Mona", Rarity.COMMON, null, null, null), + MOODY("Moody", Rarity.COMMON, null, null, null), + MOOKIE("Mookie", Rarity.COMMON, null, null, null), + MOPSY("Mopsy", Rarity.COMMON, null, null, null), + MORRIS("Morris", Rarity.COMMON, null, null, null), + MORTY("Morty", Rarity.COMMON, null, "Dungeon Hub", "Find §a15 §7unique egg locations in the §c⏣ Dungeon Hub§7."), + MOTFER("Motfer", Rarity.COMMON, null, "Dwarven Mines", null), + NATALIE("Natalie", Rarity.COMMON, null, null, null), + NED("Ned", Rarity.COMMON, null, null, null), + NIBBLES("Nibbles", Rarity.COMMON, null, null, null), + NIKO("Niko", Rarity.COMMON, null, null, null), + NIZA("Niza", Rarity.COMMON, null, null, null), + NU("Nu", Rarity.COMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + NUTMEG("Nutmeg", Rarity.COMMON, null, null, null), + OLETTA("Oletta", Rarity.COMMON, null, null, null), + OLIVER("Oliver", Rarity.COMMON, null, null, null), + OLIVETTE("Olivette", Rarity.COMMON, null, null, null), + OLIVIER("Olivier", Rarity.COMMON, null, null, null), + OLLIE("Ollie", Rarity.COMMON, null, null, null), + ONION("Onion", Rarity.COMMON, null, "Backwater Bayou", null), + PADDY("Paddy", Rarity.COMMON, null, null, null), + PATCH("Patch", Rarity.COMMON, null, null, null), + PEBBLES("Pebbles", Rarity.COMMON, null, null, null), + PENNY("Penny", Rarity.COMMON, null, null, null), + PEONY("Peony", Rarity.COMMON, null, null, null), + PETUNIA("Petunia", Rarity.COMMON, null, null, null), + PICKLES("Pickles", Rarity.COMMON, null, null, null), + PICKY("Picky", Rarity.COMMON, null, "Gold Mine", "Find §a15 §7unique egg locations in the §6⏣ Gold Mine§7."), + PINKY("Pinky", Rarity.COMMON, null, null, null), + POPPY("Poppy", Rarity.COMMON, null, null, null), + PORTER("Porter", Rarity.COMMON, null, null, null), + POTATO("Potato", Rarity.COMMON, null, "The Farming Islands", null), + QUENTIN("Quentin", Rarity.COMMON, null, null, null), + RAZZIE("Razzie", Rarity.COMMON, null, null, null), + REGINALD("Reginald", Rarity.COMMON, null, null, null), + REMI("Remi", Rarity.COMMON, null, null, null), + RESSIE("Ressie", Rarity.COMMON, null, null, null), + RICKY("Ricky", Rarity.COMMON, null, null, null), + RILEY("Riley", Rarity.COMMON, null, null, null), + ROGUE("Rogue", Rarity.COMMON, null, "Hub", null), + ROLF("Rolf", Rarity.COMMON, null, null, null), + ROSCO("Rosco", Rarity.COMMON, null, null, null), + ROSS("Ross", Rarity.COMMON, null, null, null), + ROWDY("Rowdy", Rarity.COMMON, null, null, null), + RUBEN("Ruben", Rarity.COMMON, null, null, null), + RUPERT("Rupert", Rarity.COMMON, null, null, null), + RYDER("Ryder", Rarity.COMMON, null, null, null), + SASSY("Sassy", Rarity.COMMON, null, null, null), + SCOOTER("Scooter", Rarity.COMMON, null, null, null), + SCOTCH("Scotch", Rarity.COMMON, null, null, null), + SCOUT("Scout", Rarity.COMMON, null, null, null), + SCUBA("Scuba", Rarity.COMMON, null, null, null), + SELENE("Selene", Rarity.COMMON, null, null, null), + SKIPPE("Skippe", Rarity.COMMON, null, null, null), + SMOKEY("Smokey", Rarity.COMMON, null, null, null), + SNIFFLES("Sniffles", Rarity.COMMON, null, null, null), + SNOPPY("Snoppy", Rarity.COMMON, null, null, null), + SNUFFY("Snuffy", Rarity.COMMON, null, null, null), + SOON("Soon", Rarity.COMMON, null, "Dungeon Hub", null), + SOPHIE("Sophie", Rarity.COMMON, null, null, null), + SORBET("Sorbet", Rarity.COMMON, null, null, null), + SPENCER("Spencer", Rarity.COMMON, null, null, null), + SPOT("Spot", Rarity.COMMON, null, null, null), + STANLEY("Stanley", Rarity.COMMON, null, null, null), + STINKY("Stinky", Rarity.COMMON, null, "The Farming Islands", "Find §a15 §7unique egg locations in §eThe Farming Islands§7."), + STRIKER("Striker", Rarity.COMMON, null, "Spider's Den", null), + STUART("Stuart", Rarity.COMMON, null, null, null), + SURI("Suri", Rarity.COMMON, null, null, null), + TAGALONG("Tagalong", Rarity.COMMON, null, null, null), + TAU("Tau", Rarity.COMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + TEDDY("Teddy", Rarity.COMMON, null, null, null), + THALAI("Thalai", Rarity.COMMON, null, null, null), + THEO("Theo", Rarity.COMMON, null, null, null), + THEODORE("Theodore", Rarity.COMMON, null, null, null), + THUMPER("Thumper", Rarity.COMMON, null, null, null), + TICKY("Ticky", Rarity.COMMON, null, null, null), + TOBI("Tobi", Rarity.COMMON, null, null, null), + WEBB("Webb", Rarity.COMMON, null, "Spider's Den", "Find §a15 §7unique egg locations in the §c⏣ Spider's Den§7."), + WILLIAM("William", Rarity.COMMON, null, null, null), + WINSTON("Winston", Rarity.COMMON, null, null, null), + WOODY("Woody", Rarity.COMMON, null, "The Park", "Find §a15 §7unique egg locations in the §a⏣ The Park§7."), + ZACK("Zack", Rarity.COMMON, null, null, null), + ZEA("Zea", Rarity.COMMON, null, "The End", "Find §a15 §7unique egg locations in §d⏣ The End§7."), + + // ==================== UNCOMMON RABBITS (125) ==================== + ABI("Abi", Rarity.UNCOMMON, "Obtained through getting called by §aHoppity§7.", null, null), + ABIGAIL("Abigail", Rarity.UNCOMMON, null, null, null), + ALEXA("Alexa", Rarity.UNCOMMON, null, null, null), + ALEXANDER("Alexander", Rarity.UNCOMMON, null, null, null), + ALPACA("Alpaca", Rarity.UNCOMMON, null, null, null), + AMAZON("Amazon", Rarity.UNCOMMON, null, null, null), + ARACHNO("Arachno", Rarity.UNCOMMON, null, null, "Defeat §cTarantula Broodfather Tier III§7."), + ASHES("Ashes", Rarity.UNCOMMON, null, null, null), + ASTERIX("Asterix", Rarity.UNCOMMON, null, null, null), + ATTILA("Attila", Rarity.UNCOMMON, null, "Crimson Isle", null), + AVERAGE("Average", Rarity.UNCOMMON, null, null, "Collect §a100 §7unique §fCOMMON §7Rabbits."), + AZURE("Azure", Rarity.UNCOMMON, null, "Deep Caverns", null), + BAMBAM("Bambam", Rarity.UNCOMMON, null, null, null), + BANDIT("Bandit", Rarity.UNCOMMON, null, null, null), + BARCODE("Barcode", Rarity.UNCOMMON, null, null, null), + BENJI("Benji", Rarity.UNCOMMON, null, null, null), + BETA("Beta", Rarity.UNCOMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + BILBO("Bilbo", Rarity.UNCOMMON, null, null, null), + BLOSSOM("Blossom", Rarity.UNCOMMON, null, null, null), + BLUEBERRY("Blueberry", Rarity.UNCOMMON, null, null, null), + BRICK("Brick", Rarity.UNCOMMON, null, "Gold Mine", null), + BRUTUS("Brutus", Rarity.UNCOMMON, null, null, null), + BUBBLES("Bubbles", Rarity.UNCOMMON, null, null, null), + BUCKWHEAT("Buckwheat", Rarity.UNCOMMON, null, null, null), + BUFFALO("Buffalo", Rarity.UNCOMMON, null, null, null), + BUGS("Bugs", Rarity.UNCOMMON, null, null, null), + BUMPER("Bumper", Rarity.UNCOMMON, null, null, null), + BUSTER("Buster", Rarity.UNCOMMON, null, null, null), + BUTTERS("Butters", Rarity.UNCOMMON, null, null, null), + CANDI("Candi", Rarity.UNCOMMON, null, null, null), + CARTER("Carter", Rarity.UNCOMMON, null, null, null), + CASPER("Casper", Rarity.UNCOMMON, null, null, null), + CASSIDY("Cassidy", Rarity.UNCOMMON, null, null, null), + CHARMIN("Charmin", Rarity.UNCOMMON, null, null, null), + CHEWY("Chewy", Rarity.UNCOMMON, null, null, null), + CHILLI("Chilli", Rarity.UNCOMMON, null, null, null), + CHUBBY("Chubby", Rarity.UNCOMMON, null, null, null), + CLOUDY("Cloudy", Rarity.UNCOMMON, null, null, null), + COOKIE("Cookie", Rarity.UNCOMMON, null, null, null), + COOPER("Cooper", Rarity.UNCOMMON, null, null, null), + COTTON("Cotton", Rarity.UNCOMMON, null, null, null), + COTTON_PUFF("Cotton Puff", Rarity.UNCOMMON, null, null, null), + COTTONBALL("Cottonball", Rarity.UNCOMMON, null, null, null), + DALTON("Dalton", Rarity.UNCOMMON, null, null, null), + DANDELION("Dandelion", Rarity.UNCOMMON, null, null, null), + DARLA("Darla", Rarity.UNCOMMON, null, null, null), + DASH("Dash", Rarity.UNCOMMON, null, null, null), + DEMARCUS("Demarcus", Rarity.UNCOMMON, null, null, null), + DEMETRIOUS("Demetrious", Rarity.UNCOMMON, null, null, null), + DESTINY("Destiny", Rarity.UNCOMMON, null, null, null), + DOMINO("Domino", Rarity.UNCOMMON, null, null, null), + DONKEY("Donkey", Rarity.UNCOMMON, null, "Backwater Bayou", null), + EASTWOOD("Eastwood", Rarity.UNCOMMON, null, null, null), + ELLA("Ella", Rarity.UNCOMMON, null, null, null), + ENDER("Ender", Rarity.UNCOMMON, null, "The End", null), + FIGGY("Figgy", Rarity.UNCOMMON, null, "Galatea", null), + FITCH("Fitch", Rarity.UNCOMMON, null, null, null), + FLIP_FLOP("Flip Flop", Rarity.UNCOMMON, null, null, null), + FORREST("Forrest", Rarity.UNCOMMON, null, null, null), + FUDGE_FOUNTAIN("Fudge Fountain", Rarity.UNCOMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + GADGET("Gadget", Rarity.UNCOMMON, null, null, null), + GEE_GEE("Gee-Gee", Rarity.UNCOMMON, null, null, null), + GINGER_GLAZE("Ginger Glaze", Rarity.UNCOMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + GOOFY("Goofy", Rarity.UNCOMMON, null, null, null), + GROG("Grog", Rarity.UNCOMMON, null, "Crystal Hollows", null), + HALMENROG("Halmenrog", Rarity.UNCOMMON, null, "Dwarven Mines", null), + HARMONY("Harmony", Rarity.UNCOMMON, null, null, null), + HONEY_HAZELNUT("Honey Hazelnut", Rarity.UNCOMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + HOP_A_LONG("Hop-a-long", Rarity.UNCOMMON, null, null, null), + ICING_IVY("Icing Ivy", Rarity.UNCOMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + INFERNO("Inferno", Rarity.UNCOMMON, null, null, "Defeat §cInferno Demonlord Tier III§7."), + IRENA("Irena", Rarity.UNCOMMON, null, null, null), + JASMINE_JELLO("Jasmine Jello", Rarity.UNCOMMON, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + JAZZ("Jazz", Rarity.UNCOMMON, null, null, null), + JELLY_BEAN("Jelly Bean", Rarity.UNCOMMON, null, null, null), + KOBI("Kobi", Rarity.UNCOMMON, null, null, null), + LEOPOLD("Leopold", Rarity.UNCOMMON, null, null, null), + LULU("Lulu", Rarity.UNCOMMON, null, null, null), + MAYBELLINE("Maybelline", Rarity.UNCOMMON, null, null, null), + MEDIOCRITY("Mediocrity", Rarity.UNCOMMON, null, null, "Collect §a200 §7unique §fCOMMON §7Rabbits."), + MELON("Melon", Rarity.UNCOMMON, null, "The Farming Islands", null), + MILO("Milo", Rarity.UNCOMMON, null, null, null), + MODEST("Modest", Rarity.UNCOMMON, null, null, "Collect §a50 §7unique §aUNCOMMON §7Rabbits."), + MORGAN("Morgan", Rarity.UNCOMMON, null, null, null), + OAKLEY("Oakley", Rarity.UNCOMMON, null, null, null), + OAKLY("Oakly", Rarity.UNCOMMON, null, "Hub", null), + OBELIX("Obelix", Rarity.UNCOMMON, null, null, null), + ORCHID("Orchid", Rarity.UNCOMMON, null, "The Park", null), + OREO("Oreo", Rarity.UNCOMMON, null, null, null), + OTTO("Otto", Rarity.UNCOMMON, null, null, null), + OZWALD("Ozwald", Rarity.UNCOMMON, null, null, null), + PANCAKE("Pancake", Rarity.UNCOMMON, null, null, null), + PATCHES("Patches", Rarity.UNCOMMON, null, null, null), + PENELOPE("Penelope", Rarity.UNCOMMON, null, null, null), + PEPSI("Pepsi", Rarity.UNCOMMON, null, null, null), + PILLSBURY("Pillsbury", Rarity.UNCOMMON, null, null, null), + POLKA_DOT("Polka Dot", Rarity.UNCOMMON, null, null, null), + PORSCHE("Porsche", Rarity.UNCOMMON, null, null, null), + PRESTIGE("Prestige", Rarity.UNCOMMON, null, null, "Reach §6Chocolate Factory III§7."), + PRETZEL("Pretzel", Rarity.UNCOMMON, null, null, null), + PRUDENT("Prudent", Rarity.UNCOMMON, null, null, "Collect §a100 §7unique §aUNCOMMON §7Rabbits."), + QUINCY("Quincy", Rarity.UNCOMMON, null, null, null), + RAVEN("Raven", Rarity.UNCOMMON, null, null, null), + RINGO("Ringo", Rarity.UNCOMMON, null, null, null), + RUINA("Ruina", Rarity.UNCOMMON, null, "Dungeon Hub", null), + RUSTY("Rusty", Rarity.UNCOMMON, null, null, null), + SARGENT("Sargent", Rarity.UNCOMMON, null, null, null), + SEINFIELD("Seinfield", Rarity.UNCOMMON, null, null, null), + SNOOPY("Snoopy", Rarity.UNCOMMON, null, null, null), + SPRINKLES("Sprinkles", Rarity.UNCOMMON, null, null, null), + SQUISH("Squish", Rarity.UNCOMMON, null, null, "Find §a500 §7duplicate Rabbits."), + STEWART("Stewart", Rarity.UNCOMMON, null, null, null), + SVEN("Sven", Rarity.UNCOMMON, null, null, "Defeat §9Sven Packmaster Tier III§7."), + SWEETPEA("Sweetpea", Rarity.UNCOMMON, null, null, null), + SYLVESTER("Sylvester", Rarity.UNCOMMON, null, null, null), + THETA("Theta", Rarity.UNCOMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + TOBY("Toby", Rarity.UNCOMMON, null, null, null), + TRIXIE("Trixie", Rarity.UNCOMMON, null, null, null), + UNA("Una", Rarity.UNCOMMON, null, null, null), + UPSILON("Upsilon", Rarity.UNCOMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + VOID("Void", Rarity.UNCOMMON, null, null, "Defeat §5Voidgloom Seraph Tier III§7."), + WADSWORTH("Wadsworth", Rarity.UNCOMMON, null, null, null), + WAFFLE("Waffle", Rarity.UNCOMMON, null, null, null), + XI("Xi", Rarity.UNCOMMON, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + ZOMBIE("Zombie", Rarity.UNCOMMON, null, null, "Defeat §5Revenant Horror Tier III§7."), + + // ==================== RARE RABBITS (82) ==================== + ALADDIN("Aladdin", Rarity.RARE, null, null, null), + ALOYSIUS("Aloysius", Rarity.RARE, null, null, null), + BARBIE("Barbie", Rarity.RARE, null, null, null), + BISHOP("Bishop", Rarity.RARE, null, null, null), + BLACKBERRY("Blackberry", Rarity.RARE, null, null, null), + BLACKJACK("Blackjack", Rarity.RARE, null, null, null), + BOULDER("Boulder", Rarity.RARE, null, "Gold Mine", null), + BUGATTI("Bugatti", Rarity.RARE, null, null, null), + BUN_BUN("Bun Bun", Rarity.RARE, null, null, null), + BURST("Burst", Rarity.RARE, null, null, "Find §a1,000 §7duplicate Rabbits."), + CAJUN("Cajun", Rarity.RARE, null, null, null), + CANOPUS("Canopus", Rarity.RARE, null, "The Park", null), + CARAMEL("Caramel", Rarity.RARE, null, null, null), + CARMINE("Carmine", Rarity.RARE, null, "Deep Caverns", null), + CASANOVA("Casanova", Rarity.RARE, null, null, null), + CHEVY("Chevy", Rarity.RARE, null, null, null), + CINNAMON("Cinnamon", Rarity.RARE, null, null, null), + COLTRON("Coltron", Rarity.RARE, null, "Crystal Hollows", null), + CRYSTAL("Crystal", Rarity.RARE, null, null, null), + DALLAS("Dallas", Rarity.RARE, null, null, null), + DRACO("Draco", Rarity.RARE, null, null, null), + EASTER("Easter", Rarity.RARE, null, null, null), + EGG("Egg", Rarity.RARE, null, "The End", null), + ELVIS("Elvis", Rarity.RARE, null, null, null), + FAVOR("Favor", Rarity.RARE, null, null, "Collect §a50 §7unique §9RARE §7Rabbits."), + FIGARO("Figaro", Rarity.RARE, null, null, null), + FISH_THE_RABBIT("Fish the Rabbit", Rarity.RARE, "Obtained by finding the §6Stray Rabbit §7in the Chocolate Factory menu.", null, null), + FRODO("Frodo", Rarity.RARE, null, null, null), + GAMMA("Gamma", Rarity.RARE, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + GRAVLER("Gravler", Rarity.RARE, null, "Spider's Den", null), + GREMLIN("Gremlin", Rarity.RARE, null, null, null), + HONEY("Honey", Rarity.RARE, null, null, null), + HOPE("Hope", Rarity.RARE, null, null, null), + HYDE("Hyde", Rarity.RARE, null, null, null), + IOTA("Iota", Rarity.RARE, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + JASPER("Jasper", Rarity.RARE, null, null, null), + JYNX("Jynx", Rarity.RARE, null, null, null), + KALUMDAI("Kalumdai", Rarity.RARE, null, "Dwarven Mines", null), + KIWI_KISS("Kiwi Kiss", Rarity.RARE, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + LAVENDER_LEMON("Lavender Lemon", Rarity.RARE, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + LINUS("Linus", Rarity.RARE, null, null, null), + LUMIE("Lumie", Rarity.RARE, null, "Galatea", null), + MAPLE_MIRAGE("Maple Mirage", Rarity.RARE, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + MIDNIGHT("Midnight", Rarity.RARE, null, null, null), + MONALISA("Monalisa", Rarity.RARE, null, null, null), + MURPHY("Murphy", Rarity.RARE, null, null, null), + MUSH_MUSH("Mush Mush", Rarity.RARE, null, "Dungeon Hub", null), + MUTONIO("Mutonio", Rarity.RARE, null, "The Farming Islands", null), + NEPTUNE("Neptune", Rarity.RARE, null, null, null), + NOUGAT_NEBULA("Nougat Nebula", Rarity.RARE, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + OLYMPE("Olympe", Rarity.RARE, null, null, null), + OMICRON("Omicron", Rarity.RARE, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + ONYX("Onyx", Rarity.RARE, null, null, null), + ORANGE_OBSIDIAN("Orange Obsidian", Rarity.RARE, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + ORLANDO("Orlando", Rarity.RARE, null, null, null), + PADDINGTON("Paddington", Rarity.RARE, null, null, null), + PEANUT("Peanut", Rarity.RARE, null, null, null), + PEPPER("Pepper", Rarity.RARE, null, null, null), + PHANTOM("Phantom", Rarity.RARE, null, null, null), + PHI("Phi", Rarity.RARE, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + POPCORN("Popcorn", Rarity.RARE, null, null, null), + PRIDE("Pride", Rarity.RARE, null, null, null), + PUMPKIN("Pumpkin", Rarity.RARE, null, null, null), + RIVER("River", Rarity.RARE, null, null, null), + SAGE("Sage", Rarity.RARE, null, null, null), + SHERK("Sherk", Rarity.RARE, null, "Backwater Bayou", null), + SNOWBALL("Snowball", Rarity.RARE, null, null, null), + SPIRIT("Spirit", Rarity.RARE, null, null, null), + SPOOKY("Spooky", Rarity.RARE, null, null, null), + STORMY("Stormy", Rarity.RARE, null, null, null), + SULFUR("Sulfur", Rarity.RARE, null, "Crimson Isle", null), + SUNNY("Sunny", Rarity.RARE, null, null, null), + TORNADO("Tornado", Rarity.RARE, null, null, null), + TOWNY("Towny", Rarity.RARE, null, "Hub", null), + TRICKY("Tricky", Rarity.RARE, null, null, null), + UNCLE_BUCK("Uncle Buck", Rarity.RARE, null, null, null), + VLAD("Vlad", Rarity.RARE, null, null, null), + WESSON("Wesson", Rarity.RARE, null, null, null), + WIDGET("Widget", Rarity.RARE, null, null, null), + WILLOW("Willow", Rarity.RARE, null, null, null), + ZERO("Zero", Rarity.RARE, null, null, null), + + // ==================== EPIC RABBITS (45) ==================== + ACE("Ace", Rarity.EPIC, null, null, null), + ACHILLES("Achilles", Rarity.EPIC, null, null, null), + ALPINE("Alpine", Rarity.EPIC, null, null, null), + ANGEL("Angel", Rarity.EPIC, null, null, null), + CALYPSO("Calypso", Rarity.EPIC, null, null, null), + CASTLE("Castle", Rarity.EPIC, null, "Hub", null), + CHI("Chi", Rarity.EPIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + COMET("Comet", Rarity.EPIC, null, null, null), + DELTA("Delta", Rarity.EPIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + DOJO("Dojo", Rarity.EPIC, null, "Crimson Isle", null), + EISEN("Eisen", Rarity.EPIC, null, "Crimson Isle", null), + GATSBY("Gatsby", Rarity.EPIC, null, null, null), + GROVE("Grove", Rarity.EPIC, null, "Galatea", null), + HOLE("Hole", Rarity.EPIC, null, "Gold Mine", null), + IMPLODE("Implode", Rarity.EPIC, null, null, "Find §a2,000 §7duplicate Rabbits."), + JEDI("Jedi", Rarity.EPIC, null, null, null), + KAPPA("Kappa", Rarity.EPIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + KEN("Ken", Rarity.EPIC, null, null, null), + KIERA("Kiera", Rarity.EPIC, null, null, null), + KODO("Kodo", Rarity.EPIC, null, null, null), + KRYS("Krys", Rarity.EPIC, null, "Dungeon Hub", null), + MAXIMA("Maxima", Rarity.EPIC, null, "Spider's Den", null), + MERLIN("Merlin", Rarity.EPIC, null, null, null), + NEARQUAAD("Nearquaad", Rarity.EPIC, null, "Backwater Bayou", null), + NECRON("Necron", Rarity.EPIC, null, null, "§cThe Catacombs Floor VII §7Completion"), + OCTAVIA("Octavia", Rarity.EPIC, null, "Dwarven Mines", null), + PEARL("Pearl", Rarity.EPIC, null, "The End", null), + PEPPERMINT_PEARL("Peppermint Pearl", Rarity.EPIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + PI("Pi", Rarity.EPIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + PRINCE("Prince", Rarity.EPIC, null, null, null), + PUNCH("Punch", Rarity.EPIC, null, null, null), + QUINCE_QUARK("Quince Quark", Rarity.EPIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + RAMBO("Rambo", Rarity.EPIC, null, null, null), + RASPBERRY_RIPPLE("Raspberry Ripple", Rarity.EPIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + ROCHIE("Rochie", Rarity.EPIC, null, "The Farming Islands", null), + SAGA("Saga", Rarity.EPIC, null, null, "Collect §a25 §7unique §5EPIC §7Rabbits."), + SAVANNAH("Savannah", Rarity.EPIC, null, "The Park", null), + SIMBA("Simba", Rarity.EPIC, null, null, null), + STRAWBERRY_SWIRL("Strawberry Swirl", Rarity.EPIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + THOR("Thor", Rarity.EPIC, null, null, null), + TOFFEE_TULIP("Toffee Tulip", Rarity.EPIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + TRIX("Trix", Rarity.EPIC, null, null, null), + TURBO("Turbo", Rarity.EPIC, null, null, null), + VIOLET("Violet", Rarity.EPIC, null, "Crystal Hollows", null), + + // ==================== LEGENDARY RABBITS (24) ==================== + APOLLO("Apollo", Rarity.LEGENDARY, null, null, null), + APRIL("April", Rarity.LEGENDARY, null, null, null), + ATLAS("Atlas", Rarity.LEGENDARY, null, null, null), + ECHO("Echo", Rarity.LEGENDARY, null, null, null), + EL_DORADO("El Dorado", Rarity.LEGENDARY, "Obtained by finding the §6Stray Rabbit §7in the Chocolate Factory menu.", null, null), + EPSILON("Epsilon", Rarity.LEGENDARY, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + EXTRA("Extra", Rarity.LEGENDARY, null, null, "Collect §a20 §7unique §6LEGENDARY §7Rabbits."), + GENERAL("General", Rarity.LEGENDARY, null, null, null), + HOUDINI("Houdini", Rarity.LEGENDARY, null, null, null), + KAEMAN("Kaeman", Rarity.LEGENDARY, null, null, "§cMaster Mode The Catacombs Floor VII §7Completion"), + LAMBDA("Lambda", Rarity.LEGENDARY, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + MAGIC("Magic", Rarity.LEGENDARY, null, null, null), + MYSTIC("Mystic", Rarity.LEGENDARY, null, null, null), + NOVA("Nova", Rarity.LEGENDARY, null, null, null), + PSI("Psi", Rarity.LEGENDARY, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + RHO("Rho", Rarity.LEGENDARY, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + SHADOW("Shadow", Rarity.LEGENDARY, null, null, null), + SOLOMON("Solomon", Rarity.LEGENDARY, null, null, null), + STORM("Storm", Rarity.LEGENDARY, null, null, null), + UBE_UNICORN("Ube Unicorn", Rarity.LEGENDARY, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + VANILLA_VORTEX("Vanilla Vortex", Rarity.LEGENDARY, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + WALNUT_WHIRL("Walnut Whirl", Rarity.LEGENDARY, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + XOCO_XANUDU("Xoco Xanudu", Rarity.LEGENDARY, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + YOGURT_YUCCA("Yogurt Yucca", Rarity.LEGENDARY, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + + // ==================== MYTHIC RABBITS (11) ==================== + DANTE("Dante", Rarity.MYTHIC, null, null, null), + EINSTEIN("Einstein", Rarity.MYTHIC, null, null, null), + GALAXY("Galaxy", Rarity.MYTHIC, null, null, null), + KING("King", Rarity.MYTHIC, null, null, null), + MU("Mu", Rarity.MYTHIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + NAPOLEON("Napoleon", Rarity.MYTHIC, null, null, null), + OMEGA("Omega", Rarity.MYTHIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + SIGMA("Sigma", Rarity.MYTHIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + ZEST_ZEPHYR("Zest Zephyr", Rarity.MYTHIC, "Obtained through your §6Chocolate Factory Milestones§7.", null, null), + ZETA("Zeta", Rarity.MYTHIC, "Obtained through your §6Chocolate Shop Milestones§7.", null, null), + ZORRO("Zorro", Rarity.MYTHIC, null, null, null), + + // ==================== DIVINE RABBITS (5) ==================== + AURORA("Aurora", Rarity.DIVINE, null, null, null), + CELESTIA("Celestia", Rarity.DIVINE, null, null, null), + ORION("Orion", Rarity.DIVINE, null, null, null), + STARFIRE("Starfire", Rarity.DIVINE, null, null, null), + VEGA("Vega", Rarity.DIVINE, null, null, null); + + private final String displayName; + private final Rarity rarity; + private final String obtainMethod; + private final String location; + private final String requirement; + + public int getChocolateBonus() { + return rarity.getChocolateBonus(); + } + + public double getMultiplierBonus() { + return rarity.getMultiplierBonus(); + } + + public String getColorCode() { + return rarity.getColorCode(); + } + + public String getFormattedName() { + return "§" + rarity.getColorCode() + displayName; + } + + /** + * Gets the formatted location string with appropriate color + */ + public String getFormattedLocation() { + if (location == null) return null; + return switch (location) { + case "Crimson Isle" -> "§c⏣ Crimson Isle"; + case "Deep Caverns" -> "§b⏣ Deep Caverns"; + case "Backwater Bayou" -> "§2⏣ Backwater Bayou"; + case "Gold Mine" -> "§6⏣ Gold Mine"; + case "Hub" -> "§bHub"; + case "The Park" -> "§a⏣ The Park"; + case "Crystal Hollows" -> "§5⏣ Crystal Hollows"; + case "The End" -> "§d⏣ The End"; + case "Dungeon Hub" -> "§c⏣ Dungeon Hub"; + case "Dwarven Mines" -> "§2⏣ Dwarven Mines"; + case "The Farming Islands" -> "§eThe Farming Islands"; + case "Spider's Den" -> "§c⏣ Spider's Den"; + case "Galatea" -> "§2⏣ Galatea"; + default -> "§7" + location; + }; + } + + /** + * Gets the resident label with color for the location + */ + public String getResidentLabel() { + if (location == null) return null; + return switch (location) { + case "Crimson Isle" -> "§cCrimson Isle Resident"; + case "Deep Caverns" -> "§bDeep Caverns Resident"; + case "Backwater Bayou" -> "§2Backwater Bayou Resident"; + case "Gold Mine" -> "§6Gold Mine Resident"; + case "Hub" -> "§bHub Resident"; + case "The Park" -> "§aThe Park Resident"; + case "Crystal Hollows" -> "§5Crystal Hollows Resident"; + case "The End" -> "§dThe End Resident"; + case "Dungeon Hub" -> "§cDungeon Hub Resident"; + case "Dwarven Mines" -> "§2Dwarven Mines Resident"; + case "The Farming Islands" -> "§eThe Farming Islands Resident"; + case "Spider's Den" -> "§cSpider's Den Resident"; + case "Galatea" -> "§2Galatea Resident"; + default -> "§7" + location + " Resident"; + }; + } + + public static int getTotalRabbits() { + return values().length; + } + + public static int getRabbitCountByRarity(Rarity rarity) { + int count = 0; + for (ChocolateRabbit rabbit : values()) { + if (rabbit.rarity == rarity) count++; + } + return count; + } + + @Getter + @AllArgsConstructor + public enum Rarity { + COMMON("f", "COMMON", 1, 0.002), + UNCOMMON("a", "UNCOMMON", 2, 0.003), + RARE("9", "RARE", 4, 0.004), + EPIC("5", "EPIC", 10, 0.005), + LEGENDARY("6", "LEGENDARY", 0, 0.02), + MYTHIC("d", "MYTHIC", 0, 0.05), + DIVINE("b", "DIVINE", 0, 0.03); + + private final String colorCode; + private final String displayName; + private final int chocolateBonus; + private final double multiplierBonus; + + public String getFormattedName() { + return "§" + colorCode + "§l" + displayName + " RABBIT"; + } + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateShopMilestone.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateShopMilestone.java new file mode 100644 index 000000000..a569d4ec2 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/ChocolateShopMilestone.java @@ -0,0 +1,115 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.minestom.server.item.Material; +import net.swofty.commons.StringUtility; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents chocolate shop milestones. + * Each milestone unlocks a special rabbit when spending a certain amount of chocolate in the shop. + */ +@Getter +@AllArgsConstructor +public enum ChocolateShopMilestone { + // Common milestones (White) + MILESTONE_1(1, 1_000L, "Alpha Rabbit", "f", 1, 0.002, + "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_2(2, 5_000L, "Beta Rabbit", "f", 1, 0.002, + "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_3(3, 25_000L, "Gamma Rabbit", "f", 1, 0.002, + "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_4(4, 100_000L, "Delta Rabbit", "f", 1, 0.002, + "d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2", Material.WHITE_STAINED_GLASS_PANE), + MILESTONE_5(5, 500_000L, "Epsilon Rabbit", "f", 1, 0.002, + "e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2", Material.WHITE_STAINED_GLASS_PANE), + + // Uncommon milestones (Lime/Green) + MILESTONE_6(6, 1_000_000L, "Zeta Rabbit", "a", 2, 0.003, + "f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_7(7, 2_500_000L, "Eta Rabbit", "a", 2, 0.003, + "a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_8(8, 5_000_000L, "Theta Rabbit", "a", 2, 0.003, + "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_9(9, 10_000_000L, "Iota Rabbit", "a", 2, 0.003, + "c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3", Material.LIME_STAINED_GLASS_PANE), + MILESTONE_10(10, 25_000_000L, "Kappa Rabbit", "a", 2, 0.003, + "d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3", Material.LIME_STAINED_GLASS_PANE), + + // Rare milestones (Blue) + MILESTONE_11(11, 50_000_000L, "Lambda Rabbit", "9", 4, 0.004, + "e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_12(12, 100_000_000L, "Mu Rabbit", "9", 4, 0.004, + "f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_13(13, 250_000_000L, "Nu Rabbit", "9", 4, 0.004, + "a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_14(14, 500_000_000L, "Xi Rabbit", "9", 4, 0.004, + "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4", Material.BLUE_STAINED_GLASS_PANE), + MILESTONE_15(15, 1_000_000_000L, "Omicron Rabbit", "9", 4, 0.004, + "c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4", Material.BLUE_STAINED_GLASS_PANE), + + // Epic milestones (Purple) + MILESTONE_16(16, 2_500_000_000L, "Pi Rabbit", "5", 10, 0.005, + "d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_17(17, 5_000_000_000L, "Rho Rabbit", "5", 10, 0.005, + "e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_18(18, 10_000_000_000L, "Sigma Rabbit", "5", 10, 0.005, + "f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_19(19, 25_000_000_000L, "Tau Rabbit", "5", 10, 0.005, + "a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5", Material.PURPLE_STAINED_GLASS_PANE), + MILESTONE_20(20, 50_000_000_000L, "Upsilon Rabbit", "5", 10, 0.005, + "b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5", Material.PURPLE_STAINED_GLASS_PANE), + + // Legendary milestones (Orange) + MILESTONE_21(21, 100_000_000_000L, "Phi Rabbit", "6", 0, 0.02, + "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_22(22, 150_000_000_000L, "Chi Rabbit", "6", 0, 0.02, + "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_23(23, 200_000_000_000L, "Psi Rabbit", "6", 0, 0.02, + "e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5", Material.ORANGE_STAINED_GLASS_PANE), + MILESTONE_24(24, 300_000_000_000L, "Omega Rabbit", "6", 0, 0.02, + "f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5", Material.ORANGE_STAINED_GLASS_PANE); + + private final int number; + private final long requiredSpent; + private final String rabbitName; + private final String colorCode; + private final int chocolateBonus; + private final double multiplierBonus; + private final String textureId; + private final Material glassPaneMaterial; + + private static final Map MILESTONES_BY_NUMBER = createMilestonesByNumber(); + + public String getRomanNumeral() { + return StringUtility.getAsRomanNumeral(number); + } + + public String getFormattedRequirement() { + return ChocolateFactoryHelper.formatChocolate(requiredSpent); + } + + public boolean isUnlocked(long totalSpent) { + return totalSpent >= requiredSpent; + } + + public double getProgress(long totalSpent) { + if (totalSpent >= requiredSpent) return 100.0; + return (totalSpent / (double) requiredSpent) * 100.0; + } + + public static ChocolateShopMilestone fromNumber(int number) { + return MILESTONES_BY_NUMBER.get(number); + } + + private static Map createMilestonesByNumber() { + Map milestonesByNumber = new HashMap<>(); + for (ChocolateShopMilestone milestone : values()) { + milestonesByNumber.put(milestone.number, milestone); + } + return milestonesByNumber; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggLocations.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggLocations.java new file mode 100644 index 000000000..f9efab97f --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggLocations.java @@ -0,0 +1,34 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.minestom.server.coordinate.Pos; + +@Getter +@AllArgsConstructor +public enum HoppityEggLocations { + BAZAAR_ALLEY(new Pos(-40, 70, -74), "§din the §eBazaar Alley"), + BLACKSMITH(new Pos(-38, 70, -135), "§dbehind the §bBlacksmith §dbuilding"), + COLOSSEUM(new Pos(160, 97, -71), "§datop the §bColosseum"), + WHEAT_MINION(new Pos(44, 71, -137), "§dnear the Wheat Minion"), + FARMHOUSE(new Pos(18, 70, -76), "§dnext to the §bFarmhouse"), + FISHERMANS_HUT(new Pos(169, 72, 36), "§dat the §bFisherman's Hut"), + LUMBER_PILES(new Pos(-153, 74, -40), "§dnear the lumber piles"), + EMERALD_ALTAR(new Pos(-127, 73, -126), "§don the emerald altar"), + MOUNTAIN_PATH(new Pos(-1, 144, 51), "§don the §bMountain §dpath"), + MOUNTAIN_PEAK(new Pos(-38, 193, 35), "§don the second highest §bMountain §dpeak"), + RUINS(new Pos(-186, 87, 81), "§dwithin the §bRuins"), + UNINCORPORATED(new Pos(-7, 70, 188), "§din §cUnincorporated Territory"), + CRANE(new Pos(-61, 80, -38), "§don the crane"), + DARK_AUCTION(new Pos(125, 74, 168), "§dnear the §5Dark Auction"), + EMERALD_TREEHOUSE(new Pos(147, 113, 117), "§dwithin the emerald treehouse"), + WITHER_CAGE(new Pos(161, 71, 157), "§dwithin the wither cage"), + WIZARD_TOWER(new Pos(35, 72, 79), "§dat the base of the Wizard Tower"); + + private final Pos position; + private final String locationMessage; + + public static int totalLocations() { + return values().length; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggType.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggType.java new file mode 100644 index 000000000..939ba2918 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityEggType.java @@ -0,0 +1,36 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum HoppityEggType { + BREAKFAST("Chocolate Breakfast Egg", "6", breakfastTexture()), + LUNCH("Chocolate Lunch Egg", "9", lunchTexture()), + DINNER("Chocolate Dinner Egg", "a", dinnerTexture()), + BRUNCH("Chocolate Brunch Egg", "6", breakfastTexture()), + DEJEUNER("Chocolate Déjeuner Egg", "9", lunchTexture()), + SUPPER("Chocolate Supper Egg", "a", dinnerTexture()), + HITMAN("Hitman Egg", "c", breakfastTexture()); + + private final String displayName; + private final String colorCode; + private final String textureHash; + + public String getFormattedName() { + return "§" + colorCode + displayName; + } + + private static String breakfastTexture() { + return "a49333d85b8a315d0336eb2df37d8a714ca24c51b8c6074f1b5b927deb516c24"; + } + + private static String lunchTexture() { + return "e5e36165819fd2850f98552edcd763ff986313119283c126ace0c4cc495e76a8"; + } + + private static String dinnerTexture() { + return "7ae6d2d31d8167bcaf95293b68a4acd872d66e751db5a34f2cbc6766a0356d0a"; + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityHuntManager.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityHuntManager.java new file mode 100644 index 000000000..982899ce7 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/chocolatefactory/HoppityHuntManager.java @@ -0,0 +1,197 @@ +package net.swofty.type.skyblockgeneric.chocolatefactory; + +import lombok.Getter; +import lombok.Setter; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.concurrent.ThreadLocalRandom; + +public class HoppityHuntManager { + private static final String HUB_LOCATION = "Hub"; + private static final String EGG_ALREADY_CLAIMED_MESSAGE = "§cYou have already claimed this egg!"; + private static final String NEW_RABBIT_MESSAGE_PREFIX = "§d§lNEW RABBIT! §r§"; + private static final String DUPLICATE_RABBIT_MESSAGE_PREFIX = "§d§lDUPLICATE RABBIT! §r§"; + private static final String HUNT_MESSAGE_PREFIX = "§d§lHOPPITY'S HUNT §r"; + private static final int DEFAULT_DUPLICATE_MULTIPLIER = 150; + private static final double ONE_MILLION = 1_000_000.0; + private static final int ONE_THOUSAND = 1_000; + + private static final HoppityEggType[] HUNT_EGG_TYPES = { + HoppityEggType.BREAKFAST, HoppityEggType.LUNCH, HoppityEggType.DINNER, + HoppityEggType.BRUNCH, HoppityEggType.DEJEUNER, HoppityEggType.SUPPER + }; + + @Getter + private static final HoppityHuntManager instance = new HoppityHuntManager(); + + @Setter + private static Runnable onHuntStartCallback; + @Setter + private static Runnable onHuntStopCallback; + + @Getter + private boolean active; + @Getter + private final Map locationEggTypes = new EnumMap<>(HoppityEggLocations.class); + + private static final NavigableMap RARITY_WEIGHTS = createRarityWeights(); + private static final Map DUPLICATE_MULTIPLIERS = createDuplicateMultipliers(); + private static final Map> ELIGIBLE_RABBITS = createEligibleRabbits(); + + private HoppityHuntManager() { + } + + public void startHunt() { + if (active) return; + + active = true; + locationEggTypes.clear(); + + HoppityEggLocations[] locations = HoppityEggLocations.values(); + for (int i = 0; i < locations.length; i++) { + locationEggTypes.put(locations[i], HUNT_EGG_TYPES[i % HUNT_EGG_TYPES.length]); + } + + runCallback(onHuntStartCallback); + } + + public void stopHunt() { + if (!active) return; + + active = false; + + runCallback(onHuntStopCallback); + + locationEggTypes.clear(); + } + + public void claimEgg(SkyBlockPlayer player, HoppityEggLocations location) { + if (!active) return; + + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (data.hasClaimedEgg(location.name())) { + player.sendMessage(EGG_ALREADY_CLAIMED_MESSAGE); + return; + } + + HoppityEggType eggType = locationEggTypes.get(location); + if (eggType == null) return; + + data.claimEgg(location.name()); + + ChocolateRabbit.Rarity rarity = rollRarity(); + ChocolateRabbit rabbit = rollRabbit(rarity); + + player.sendMessage(HUNT_MESSAGE_PREFIX + eggType.getFormattedName() + " §r" + location.getLocationMessage()); + + if (rabbit != null && !data.hasFoundRabbit(rabbit.name())) { + data.addFoundRabbit(rabbit.name()); + player.sendMessage(NEW_RABBIT_MESSAGE_PREFIX + rarity.getColorCode() + rabbit.getDisplayName() + " §7(" + rarity.getFormattedName() + "§7)"); + player.sendMessage("§7+§6" + rarity.getChocolateBonus() + " Chocolate §7per second"); + } else { + long duplicateChocolate = getDuplicateChocolate(rarity, data); + data.addChocolate(duplicateChocolate); + String rabbitName = rabbit != null ? rabbit.getDisplayName() : "Unknown"; + player.sendMessage(DUPLICATE_RABBIT_MESSAGE_PREFIX + rarity.getColorCode() + rabbitName); + player.sendMessage("§7+§6" + formatNumber(duplicateChocolate) + " Chocolate"); + } + + ChocolateFactoryHelper.getDatapoint(player).setValue(data); + } + + private ChocolateRabbit.Rarity rollRarity() { + double totalWeight = RARITY_WEIGHTS.lastKey(); + double roll = ThreadLocalRandom.current().nextDouble() * totalWeight; + return RARITY_WEIGHTS.higherEntry(roll).getValue(); + } + + private ChocolateRabbit rollRabbit(ChocolateRabbit.Rarity rarity) { + List eligible = ELIGIBLE_RABBITS.getOrDefault(rarity, List.of()); + if (eligible.isEmpty()) return null; + return eligible.get(ThreadLocalRandom.current().nextInt(eligible.size())); + } + + private long getDuplicateChocolate(ChocolateRabbit.Rarity rarity, DatapointChocolateFactory.ChocolateFactoryData data) { + int multiplier = DUPLICATE_MULTIPLIERS.getOrDefault(rarity, DEFAULT_DUPLICATE_MULTIPLIER); + double cps = data.getChocolatePerSecond(); + return Math.max(multiplier, (long) (cps * multiplier)); + } + + private String formatNumber(long number) { + if (number >= ONE_MILLION) { + return String.format("%.1fM", number / ONE_MILLION); + } + if (number >= ONE_THOUSAND) { + return String.format("%,d", number); + } + return String.valueOf(number); + } + + private static NavigableMap createRarityWeights() { + NavigableMap weights = new TreeMap<>(); + double cumulative = 0; + cumulative += 608; + weights.put(cumulative, ChocolateRabbit.Rarity.COMMON); + cumulative += 250; + weights.put(cumulative, ChocolateRabbit.Rarity.UNCOMMON); + cumulative += 100; + weights.put(cumulative, ChocolateRabbit.Rarity.RARE); + cumulative += 30; + weights.put(cumulative, ChocolateRabbit.Rarity.EPIC); + cumulative += 10; + weights.put(cumulative, ChocolateRabbit.Rarity.LEGENDARY); + cumulative += 2; + weights.put(cumulative, ChocolateRabbit.Rarity.MYTHIC); + cumulative += 0.5; + weights.put(cumulative, ChocolateRabbit.Rarity.DIVINE); + return weights; + } + + private static Map createDuplicateMultipliers() { + Map multipliers = new EnumMap<>(ChocolateRabbit.Rarity.class); + multipliers.put(ChocolateRabbit.Rarity.COMMON, 150); + multipliers.put(ChocolateRabbit.Rarity.UNCOMMON, 300); + multipliers.put(ChocolateRabbit.Rarity.RARE, 750); + multipliers.put(ChocolateRabbit.Rarity.EPIC, 1500); + multipliers.put(ChocolateRabbit.Rarity.LEGENDARY, 4000); + multipliers.put(ChocolateRabbit.Rarity.MYTHIC, 8000); + multipliers.put(ChocolateRabbit.Rarity.DIVINE, 15000); + return multipliers; + } + + private static Map> createEligibleRabbits() { + Map> rabbitsByRarity = new EnumMap<>(ChocolateRabbit.Rarity.class); + for (ChocolateRabbit.Rarity rarity : ChocolateRabbit.Rarity.values()) { + rabbitsByRarity.put(rarity, new ArrayList<>()); + } + + for (ChocolateRabbit rabbit : ChocolateRabbit.values()) { + if (rabbit.getObtainMethod() != null) { + continue; + } + + String rabbitLocation = rabbit.getLocation(); + if (rabbitLocation != null && !HUB_LOCATION.equals(rabbitLocation)) { + continue; + } + + rabbitsByRarity.get(rabbit.getRarity()).add(rabbit); + } + + return rabbitsByRarity; + } + + private void runCallback(Runnable callback) { + if (callback != null) { + callback.run(); + } + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/ChocolateFactoryCommand.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/ChocolateFactoryCommand.java new file mode 100644 index 000000000..b55d5ccd1 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/ChocolateFactoryCommand.java @@ -0,0 +1,26 @@ +package net.swofty.type.skyblockgeneric.commands; + +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.gui.inventories.GUIChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.time.Duration; + +@CommandParameters(aliases = "cf factory", + description = "Opens the Chocolate Factory menu", + usage = "/chocolatefactory", + permission = Rank.DEFAULT, + allowsConsole = false) +public class ChocolateFactoryCommand extends HypixelCommand { + @Override + public void registerUsage(MinestomCommand command) { + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + + SkyBlockPlayer player = (SkyBlockPlayer) sender; + player.openView(new GUIChocolateFactory()).refreshEvery(Duration.ofSeconds(1)); + }); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/HoppityHuntCommand.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/HoppityHuntCommand.java new file mode 100644 index 000000000..16d69dda6 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/commands/HoppityHuntCommand.java @@ -0,0 +1,47 @@ +package net.swofty.type.skyblockgeneric.commands; + +import net.minestom.server.command.builder.arguments.ArgumentType; +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.chocolatefactory.HoppityHuntManager; + +@CommandParameters(description = "Manages the Hoppity Hunt event", + usage = "/hoppityhunt ", + permission = Rank.STAFF, + allowsConsole = false) +public class HoppityHuntCommand extends HypixelCommand { + private static final String HUNT_ALREADY_ACTIVE_MESSAGE = "§cHoppity's Hunt is already active!"; + private static final String HUNT_NOT_ACTIVE_MESSAGE = "§cHoppity's Hunt is not currently active!"; + private static final String HUNT_STARTED_MESSAGE = "§aHoppity's Hunt has been started! §e17 eggs §ahave been spawned."; + private static final String HUNT_STOPPED_MESSAGE = "§aHoppity's Hunt has been stopped! All eggs have been removed."; + + @Override + public void registerUsage(MinestomCommand command) { + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + + HoppityHuntManager manager = HoppityHuntManager.getInstance(); + if (manager.isActive()) { + sender.sendMessage(HUNT_ALREADY_ACTIVE_MESSAGE); + return; + } + + manager.startHunt(); + sender.sendMessage(HUNT_STARTED_MESSAGE); + }, ArgumentType.Literal("start")); + + command.addSyntax((sender, context) -> { + if (!permissionCheck(sender)) return; + + HoppityHuntManager manager = HoppityHuntManager.getInstance(); + if (!manager.isActive()) { + sender.sendMessage(HUNT_NOT_ACTIVE_MESSAGE); + return; + } + + manager.stopHunt(); + sender.sendMessage(HUNT_STOPPED_MESSAGE); + }, ArgumentType.Literal("stop")); + } +} 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..cf53969a5 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 @@ -457,6 +457,9 @@ DatapointStash.class, new DatapointStash("stash")), COLLECTED_MOB_TYPE_REWARDS("collected_mob_type_rewards", false, false, false, DatapointCollectedMobTypeRewards.class, new DatapointCollectedMobTypeRewards("collected_mob_type_rewards")), + + CHOCOLATE_FACTORY("chocolate_factory", false, false, false, + DatapointChocolateFactory.class, new DatapointChocolateFactory("chocolate_factory")), ; @Getter private final String key; diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointChocolateFactory.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointChocolateFactory.java new file mode 100644 index 000000000..6569bd210 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/data/datapoints/DatapointChocolateFactory.java @@ -0,0 +1,462 @@ +package net.swofty.type.skyblockgeneric.data.datapoints; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.swofty.commons.protocol.Serializer; +import net.swofty.type.skyblockgeneric.data.SkyBlockDatapoint; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class DatapointChocolateFactory extends SkyBlockDatapoint { + private static final Serializer serializer = new Serializer<>() { + @Override + public String serialize(ChocolateFactoryData value) { + JSONObject json = new JSONObject(); + + json.put("chocolate", value.getChocolate()); + json.put("chocolateAllTime", value.getChocolateAllTime()); + json.put("lastUpdated", value.getLastUpdated()); + json.put("partialChocolate", value.getPartialChocolate()); + + // Upgrades + json.put("timeTowerLevel", value.getTimeTowerLevel()); + json.put("timeTowerCharges", value.getTimeTowerCharges()); + json.put("timeTowerLastUsed", value.getTimeTowerLastUsed()); + json.put("timeTowerActiveUntil", value.getTimeTowerActiveUntil()); + json.put("rabbitBarnLevel", value.getRabbitBarnLevel()); + json.put("handBakedChocolateLevel", value.getHandBakedChocolateLevel()); + json.put("rabbitShrineLevel", value.getRabbitShrineLevel()); + json.put("coachJackrabbitLevel", value.getCoachJackrabbitLevel()); + json.put("employees", serializeEmployees(value.getEmployees())); + + // Production stats + json.put("totalClicks", value.getTotalClicks()); + json.put("totalTimeTowerUsages", value.getTotalTimeTowerUsages()); + json.put("foundRabbits", serializeStringSet(value.getFoundRabbits())); + + // Shop stats + json.put("totalChocolateSpent", value.getTotalChocolateSpent()); + + // Hoppity Hunt + json.put("claimedEggs", serializeStringSet(value.getClaimedEggs())); + json.put("totalEggsFound", value.getTotalEggsFound()); + + return json.toString(); + } + + @Override + public ChocolateFactoryData deserialize(String json) { + if (json == null || json.isEmpty()) { + return new ChocolateFactoryData(); + } + + JSONObject jsonObject = new JSONObject(json); + Map employees = deserializeEmployees(jsonObject.optJSONObject("employees")); + Set foundRabbits = deserializeStringSet(jsonObject.optJSONArray("foundRabbits")); + Set claimedEggs = deserializeStringSet(jsonObject.optJSONArray("claimedEggs")); + + return new ChocolateFactoryData( + jsonObject.optLong("chocolate", 0L), + jsonObject.optLong("chocolateAllTime", 0L), + jsonObject.optLong("lastUpdated", System.currentTimeMillis()), + jsonObject.optDouble("partialChocolate", 0.0), + jsonObject.optInt("timeTowerLevel", 0), + jsonObject.optInt("timeTowerCharges", 0), + jsonObject.optLong("timeTowerLastUsed", 0L), + jsonObject.optLong("timeTowerActiveUntil", 0L), + jsonObject.optInt("rabbitBarnLevel", 0), + jsonObject.optInt("handBakedChocolateLevel", 0), + jsonObject.optInt("rabbitShrineLevel", 0), + jsonObject.optInt("coachJackrabbitLevel", 0), + employees, + jsonObject.optLong("totalClicks", 0L), + jsonObject.optInt("totalTimeTowerUsages", 0), + foundRabbits, + jsonObject.optLong("totalChocolateSpent", 0L), + claimedEggs, + jsonObject.optInt("totalEggsFound", 0) + ); + } + + @Override + public ChocolateFactoryData clone(ChocolateFactoryData value) { + Map clonedEmployees = cloneEmployees(value.getEmployees()); + + return new ChocolateFactoryData( + value.getChocolate(), + value.getChocolateAllTime(), + value.getLastUpdated(), + value.getPartialChocolate(), + value.getTimeTowerLevel(), + value.getTimeTowerCharges(), + value.getTimeTowerLastUsed(), + value.getTimeTowerActiveUntil(), + value.getRabbitBarnLevel(), + value.getHandBakedChocolateLevel(), + value.getRabbitShrineLevel(), + value.getCoachJackrabbitLevel(), + clonedEmployees, + value.getTotalClicks(), + value.getTotalTimeTowerUsages(), + new HashSet<>(value.getFoundRabbits()), + value.getTotalChocolateSpent(), + new HashSet<>(value.getClaimedEggs()), + value.getTotalEggsFound() + ); + } + + private JSONObject serializeEmployees(Map employees) { + JSONObject employeesJson = new JSONObject(); + for (Map.Entry entry : employees.entrySet()) { + JSONObject employeeJson = new JSONObject(); + EmployeeData employee = entry.getValue(); + employeeJson.put("level", employee.getLevel()); + employeeJson.put("baseProduction", employee.getBaseProduction()); + employeesJson.put(entry.getKey(), employeeJson); + } + return employeesJson; + } + + private Map deserializeEmployees(JSONObject employeesJson) { + Map employees = new HashMap<>(); + if (employeesJson == null) { + return employees; + } + + for (String key : employeesJson.keySet()) { + JSONObject employeeJson = employeesJson.getJSONObject(key); + employees.put(key, new EmployeeData( + employeeJson.optInt("level", 0), + employeeJson.optDouble("baseProduction", 0.0) + )); + } + return employees; + } + + private JSONArray serializeStringSet(Set values) { + JSONArray array = new JSONArray(); + for (String value : values) { + array.put(value); + } + return array; + } + + private Set deserializeStringSet(JSONArray array) { + Set values = new HashSet<>(); + if (array == null) { + return values; + } + + for (int i = 0; i < array.length(); i++) { + values.add(array.getString(i)); + } + return values; + } + + private Map cloneEmployees(Map employees) { + Map clonedEmployees = new HashMap<>(); + for (Map.Entry entry : employees.entrySet()) { + EmployeeData employee = entry.getValue(); + clonedEmployees.put(entry.getKey(), new EmployeeData( + employee.getLevel(), + employee.getBaseProduction() + )); + } + return clonedEmployees; + } + }; + + public DatapointChocolateFactory(String key, ChocolateFactoryData value, Serializer serializer) { + super(key, value, serializer); + } + + public DatapointChocolateFactory(String key, ChocolateFactoryData value) { + super(key, value, serializer); + } + + public DatapointChocolateFactory(String key) { + super(key, new ChocolateFactoryData(), serializer); + } + + @AllArgsConstructor + @NoArgsConstructor + @Getter + @Setter + public static class ChocolateFactoryData { + private static final int BASE_CLICK_POWER = 1; + private static final int BASE_RABBIT_SLOTS = 3; + private static final double BASE_MULTIPLIER = 1.0; + private static final double SHRINE_MULTIPLIER_PER_LEVEL = 0.1; + private static final double TIME_TOWER_MULTIPLIER_PER_LEVEL = 0.1; + private static final double COACH_MULTIPLIER_PER_LEVEL = 0.01; + private static final long TIME_TOWER_DURATION_MS = 60L * 60L * 1000L; + private static final double MILLIS_PER_SECOND = 1000.0; + + private long chocolate; + private long chocolateAllTime; + private long lastUpdated = System.currentTimeMillis(); + private double partialChocolate; // Accumulates fractional chocolate production + + // Upgrades + private int timeTowerLevel; + private int timeTowerCharges; + private long timeTowerLastUsed; + private long timeTowerActiveUntil; + private int rabbitBarnLevel; + private int handBakedChocolateLevel; + private int rabbitShrineLevel; + private int coachJackrabbitLevel; + + // Employees (rabbit name -> employee data) + private Map employees = new HashMap<>(); + + // Statistics + private long totalClicks; + private int totalTimeTowerUsages; + + // Collection + private Set foundRabbits = new HashSet<>(); + + // Shop stats + private long totalChocolateSpent; + + // Hoppity Hunt + private Set claimedEggs = new HashSet<>(); + private int totalEggsFound; + + /** + * Adds chocolate and updates all-time total + */ + public void addChocolate(long amount) { + this.chocolate += amount; + if (amount > 0) { + this.chocolateAllTime += amount; + } + } + + /** + * Removes chocolate (for purchases) + */ + public boolean removeChocolate(long amount) { + if (this.chocolate >= amount) { + this.chocolate -= amount; + return true; + } + return false; + } + + /** + * Gets the click power (chocolate per click) + * Base is 1, increases with Hand-Baked Chocolate upgrade + */ + public int getClickPower() { + return BASE_CLICK_POWER + handBakedChocolateLevel; + } + + /** + * Gets the maximum number of rabbit slots based on Rabbit Barn level + */ + public int getMaxRabbitSlots() { + return BASE_RABBIT_SLOTS + rabbitBarnLevel; + } + + /** + * Gets the production multiplier from Rabbit Shrine + * Base is 1.0, increases by 0.1 per level + */ + public double getShrineMultiplier() { + return BASE_MULTIPLIER + (rabbitShrineLevel * SHRINE_MULTIPLIER_PER_LEVEL); + } + + /** + * Gets the Time Tower multiplier when active + * Base is 1.0, increases by 0.1 per level + */ + public double getTimeTowerMultiplier() { + if (System.currentTimeMillis() < timeTowerActiveUntil) { + return BASE_MULTIPLIER + (timeTowerLevel * TIME_TOWER_MULTIPLIER_PER_LEVEL); + } + return BASE_MULTIPLIER; + } + + /** + * Checks if Time Tower is currently active + */ + public boolean isTimeTowerActive() { + return System.currentTimeMillis() < timeTowerActiveUntil; + } + + /** + * Activates the Time Tower if charges are available + */ + public boolean activateTimeTower() { + if (timeTowerCharges > 0 && !isTimeTowerActive()) { + long now = System.currentTimeMillis(); + timeTowerCharges--; + timeTowerLastUsed = now; + timeTowerActiveUntil = now + TIME_TOWER_DURATION_MS; // 1 hour + totalTimeTowerUsages++; + return true; + } + return false; + } + + /** + * Gets the Coach Jackrabbit multiplier + * Base is 1.0, increases by 0.01 per level + */ + public double getCoachMultiplier() { + return BASE_MULTIPLIER + (coachJackrabbitLevel * COACH_MULTIPLIER_PER_LEVEL); + } + + /** + * Calculates total chocolate production per second from all sources + */ + public double getChocolatePerSecond() { + double baseProduction = 0; + + // Sum production from all employees + for (EmployeeData employee : employees.values()) { + baseProduction += employee.getProductionPerSecond(); + } + + // Apply multipliers + baseProduction *= getShrineMultiplier(); + baseProduction *= getTimeTowerMultiplier(); + baseProduction *= getCoachMultiplier(); + + return baseProduction; + } + + /** + * Updates chocolate based on time passed since last update + */ + public void updateChocolateFromProduction() { + long now = System.currentTimeMillis(); + double secondsPassed = (now - lastUpdated) / MILLIS_PER_SECOND; + + if (secondsPassed > 0) { + // Accumulate fractional production + partialChocolate += getChocolatePerSecond() * secondsPassed; + + // Only add whole chocolate + long wholeChocolate = (long) partialChocolate; + if (wholeChocolate > 0) { + addChocolate(wholeChocolate); + partialChocolate -= wholeChocolate; // Keep the fractional part + } + + lastUpdated = now; + } + } + + /** + * Registers a click and adds chocolate + */ + public void click() { + addChocolate(getClickPower()); + totalClicks++; + } + + /** + * Hires or upgrades an employee + */ + public void setEmployee(String name, int level, double baseProduction) { + employees.put(name, new EmployeeData(level, baseProduction)); + } + + /** + * Gets employee count + */ + public int getEmployeeCount() { + return employees.size(); + } + + /** + * Gets prestige level based on all-time chocolate + */ + public int getPrestigeLevel() { + if (chocolateAllTime >= 1_000_000_000_000L) return 6; // 1T + if (chocolateAllTime >= 10_000_000_000L) return 5; // 10B + if (chocolateAllTime >= 1_000_000_000L) return 4; // 1B + if (chocolateAllTime >= 100_000_000L) return 3; // 100M + if (chocolateAllTime >= 10_000_000L) return 2; // 10M + if (chocolateAllTime >= 1_000_000L) return 1; // 1M + return 0; + } + + /** + * Adds a rabbit to the found collection + */ + public void addFoundRabbit(String rabbitName) { + foundRabbits.add(rabbitName); + } + + /** + * Checks if a rabbit has been found + */ + public boolean hasFoundRabbit(String rabbitName) { + return foundRabbits.contains(rabbitName); + } + + /** + * Gets the count of found rabbits + */ + public int getFoundRabbitCount() { + return foundRabbits.size(); + } + + /** + * Adds to total chocolate spent (for shop milestones tracking) + */ + public void addChocolateSpent(long amount) { + this.totalChocolateSpent += amount; + } + + /** + * Claims an egg location for the current hunt + */ + public void claimEgg(String eggLocationId) { + claimedEggs.add(eggLocationId); + totalEggsFound++; + } + + /** + * Checks if an egg location has been claimed in the current hunt + */ + public boolean hasClaimedEgg(String eggLocationId) { + return claimedEggs.contains(eggLocationId); + } + + /** + * Resets claimed eggs for a new hunt + */ + public void resetClaimedEggs() { + claimedEggs.clear(); + } + } + + @AllArgsConstructor + @NoArgsConstructor + @Getter + @Setter + public static class EmployeeData { + private int level; + private double baseProduction; + + /** + * Gets production per second for this employee + * Production = baseProduction * level + */ + public double getProductionPerSecond() { + return baseProduction * level; + } + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactory.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactory.java new file mode 100644 index 000000000..f84362db7 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactory.java @@ -0,0 +1,747 @@ +package net.swofty.type.skyblockgeneric.gui.inventories; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +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.*; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateRabbit; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIChocolateFactory implements StatefulView { + private static final int RABBIT_BARN_MAX_LEVEL = 247; + private static final int RABBIT_BARN_EXTRA_CAPACITY = 2; + private static final int HAND_BAKED_MAX_LEVEL = 10; + private static final int TIME_TOWER_MAX_LEVEL = 15; + private static final int RABBIT_SHRINE_MAX_LEVEL = 20; + private static final int COACH_JACKRABBIT_MAX_LEVEL = 20; + private static final int EMPLOYEE_MAX_LEVEL = 220; + private static final int TOTAL_RABBITS = ChocolateRabbit.values().length; + private static final long MILLIS_PER_MINUTE = 60_000L; + private static final long MILLIS_PER_SECOND = 1_000L; + + private static final String NOT_ENOUGH_CHOCOLATE_MESSAGE = "§cYou don't have enough Chocolate!"; + + // Texture IDs + private static final String CHOCOLATE_TEXTURE = "9a815398e7da89b1bc08f646cafc8e7b813da0be0eec0cce6d3eff5207801026"; + private static final String HOPPITY_TEXTURE = "b79e7f3341b672d9de6564cbaca052a6a723ea466a2e66af35ba1ba855f0d692"; + private static final String COACH_JACKRABBIT_TEXTURE = "bc0cc67e79c228e541e68aeb1d81ed7af51166622ad4db9417d7a29d1b89af95"; + + private static final Sound CLICK_SOUND = Sound.sound(Key.key("block.note_block.bit"), Sound.Source.PLAYER, 1.0f, 1.21f); + private static final Sound NOT_ENOUGH_CHOCOLATE_SOUND = Sound.sound(Key.key("entity.enderman.teleport"), Sound.Source.PLAYER, 8.0f, 0.0f); + private static final Sound UPGRADE_SOUND = Sound.sound(Key.key("block.note_block.pling"), Sound.Source.PLAYER, 8.0f, 4.05f); + + // Employee slots (slots 28-34) + private static final int[] EMPLOYEE_SLOTS = {28, 29, 30, 31, 32, 33, 34}; + private static final String[] EMPLOYEE_NAMES = { + "Rabbit Bro", "Rabbit Cousin", "Rabbit Sis", "Rabbit Daddy", + "Rabbit Granny", "Rabbit Uncle", "Rabbit Dog" + }; + private static final String[] EMPLOYEE_TEXTURES = { + "287934bdd9df2705b251bb997e029b18c1e94df12992b8107e74497b205ca7e8", + "a982825c01b658f348a099b4579029a180d2e415183951b2e6e5e27257df4254", + "fd076e0e3d4072d0fffee0a87a5d726fc34b2bcec38c264fb9b67871a8ead633", + "57cab0c34d7ddcf72db56ff36f2883f554cff76eb5d3b3e0562338036c976043", + "d6eb2d85ee8e3af1c2ec934beb70a39c5e766b23bdab63210bd2aacd73cbbfc8", + "a865176723a0b9ee2916180a55a04cccb7704ad1f31fdf3e9d89c798f6802e6b", + "35ca98bede3865dd1205e4d091036cd9dc36791b83ea4e0ff4a99ad61b71e898" + }; + private static final String[] EMPLOYEE_SUBTITLES = { + "Ambition on two feet!", "Laid-back legend!", "Rebel with a cause!", + "CFO with nerves of steel!", "Storyteller supreme!", + "Stuck in a highlight reel!", "Making chocolate, not eating it!" + }; + + public record State() {} + + @Override + public State initialState() { + return new State(); + } + + @Override + public ViewConfiguration configuration() { + return ViewConfiguration.withString((state, ctx) -> "Chocolate Factory", InventoryType.CHEST_6_ROW); + } + + @Override + public void onOpen(State state, ViewContext ctx) { + // Update production based on time elapsed (handles offline production) + ChocolateFactoryHelper.updateProduction((SkyBlockPlayer) ctx.player()); + } + + @Override + public void onRefresh(State state, ViewContext ctx) { + ChocolateFactoryHelper.updateProduction((SkyBlockPlayer) ctx.player()); + } + + @Override + public void layout(ViewLayout layout, State state, ViewContext ctx) { + Components.fill(layout); + + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + ChocolateFactoryHelper.updateProduction(player); + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + // Slot 13: Chocolate cookie (clickable) + layout.slot(13, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + List lore = new ArrayList<>(); + lore.add("§7§6Chocolate§7, of course, is not a valid"); + lore.add("§7source of §anutrition§7. This, however,"); + lore.add("§7does not stop it from being §dawesome§7."); + lore.add(""); + lore.add("§7Chocolate Production"); + lore.add("§6" + String.format("%.2f", d.getChocolatePerSecond()) + " §8per second"); + lore.add(""); + lore.add("§7All-time Chocolate: §6" + ChocolateFactoryHelper.formatChocolate(d.getChocolateAllTime())); + lore.add(""); + lore.add("§eClick to uncover the meaning of life!"); + + return ItemStackCreator.getStackHead( + "§e" + ChocolateFactoryHelper.formatChocolate(d.getChocolate()) + " §6Chocolate", + CHOCOLATE_TEXTURE, 1, lore); + }, (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + ChocolateFactoryHelper.handleClick(p); + p.playSound(CLICK_SOUND); + c.session(State.class).refresh(); + }); + + // Slot 27: Prestige/Chocolate Factory level + layout.slot(27, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + int prestigeLevel = d.getPrestigeLevel(); + String prestigeColor = ChocolateFactoryHelper.getPrestigeRankColor(prestigeLevel); + + List lore = new ArrayList<>(); + lore.add("§7Chocolate Production Multiplier: §6" + String.format("%.1fx", d.getShrineMultiplier() * d.getCoachMultiplier())); + lore.add("§7Max Rabbit Rarity: §b§lDIVINE"); + lore.add("§7Max Chocolate: §660B"); + lore.add("§7Max Employee: §7[220§7] §bBoard Member"); + lore.add("§7Max §cRabbit Hitman §7Slots: §628"); + lore.add(""); + lore.add("§7Chocolate this Prestige: §6" + ChocolateFactoryHelper.formatChocolate(d.getChocolateAllTime())); + lore.add(""); + lore.add(prestigeLevel >= 6 ? "§aYou have reached max prestige!" : "§eClick to prestige!"); + + return ItemStackCreator.getStack(prestigeColor + "Chocolate Factory " + StringUtility.getAsRomanNumeral(prestigeLevel + 1), Material.DROPPER, 1, lore); + }); + + // Employee slots (28-34) + setupEmployeeSlots(layout); + + // Slot 35: Rabbit Barn + layout.slot(35, (s, c) -> createRabbitBarnItem((SkyBlockPlayer) c.player()), (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + if (d.getRabbitBarnLevel() >= RABBIT_BARN_MAX_LEVEL) { + p.sendMessage("§cYour Rabbit Barn is already at maximum capacity!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (ChocolateFactoryHelper.purchaseUpgrade(p, ChocolateFactoryHelper.UpgradeType.RABBIT_BARN)) { + d = ChocolateFactoryHelper.getData(p); + p.sendMessage("§7Your §aRabbit Barn §7capacity has been increased to §a" + (d.getMaxRabbitSlots() + RABBIT_BARN_EXTRA_CAPACITY) + " Rabbits§7!"); + p.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(p); + } + c.session(State.class).refresh(); + }); + + // Slot 38: Hand-Baked Chocolate + layout.slot(38, (s, c) -> createHandBakedChocolateItem((SkyBlockPlayer) c.player()), (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + if (d.getHandBakedChocolateLevel() >= HAND_BAKED_MAX_LEVEL) { + p.sendMessage("§cYou only have so many fingers! You can't click any faster!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (ChocolateFactoryHelper.purchaseUpgrade(p, ChocolateFactoryHelper.UpgradeType.HAND_BAKED_CHOCOLATE)) { + d = ChocolateFactoryHelper.getData(p); + p.sendMessage("§7You will now produce §6+" + d.getClickPower() + " Chocolate §7per click!"); + p.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(p); + } + c.session(State.class).refresh(); + }); + + // Slot 39: Time Tower + layout.slot(39, (s, c) -> createTimeTowerItem((SkyBlockPlayer) c.player()), (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + if (d.getPrestigeLevel() < 1) { + p.sendMessage("§cThis requires Chocolate Factory II!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + // Right-click to activate + if (click.click() instanceof Click.Right && d.getTimeTowerLevel() > 0 && d.getTimeTowerCharges() > 0 && !d.isTimeTowerActive()) { + if (ChocolateFactoryHelper.activateTimeTower(p)) { + p.sendMessage("§aTime Tower activated for 1 hour!"); + p.playSound(Sound.sound(Key.key("block.beacon.activate"), Sound.Source.PLAYER, 1.0f, 1.0f)); + c.session(State.class).refresh(); + return; + } + } + + // Left-click to upgrade + if (d.getTimeTowerLevel() >= TIME_TOWER_MAX_LEVEL) { + p.sendMessage("§cThe Time Tower is already at its maximum level!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + } else if (ChocolateFactoryHelper.purchaseUpgrade(p, ChocolateFactoryHelper.UpgradeType.TIME_TOWER)) { + p.sendMessage("§7You upgraded to §dTime Tower " + StringUtility.getAsRomanNumeral(d.getTimeTowerLevel() + 1) + "§7!"); + p.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(p); + } + c.session(State.class).refresh(); + }); + + // Slot 41: Rabbit Shrine + layout.slot(41, (s, c) -> createRabbitShrineItem((SkyBlockPlayer) c.player()), (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + if (d.getPrestigeLevel() < 2) { + p.sendMessage("§cThis requires Chocolate Factory III!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (d.getRabbitShrineLevel() >= RABBIT_SHRINE_MAX_LEVEL) { + p.sendMessage("§cYour Rabbit Shrine is already at its maximum level!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (ChocolateFactoryHelper.purchaseUpgrade(p, ChocolateFactoryHelper.UpgradeType.RABBIT_SHRINE)) { + p.sendMessage("§aUpgraded Rabbit Shrine!"); + p.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(p); + } + c.session(State.class).refresh(); + }); + + // Slot 42: Coach Jackrabbit + layout.slot(42, (s, c) -> createCoachJackrabbitItem((SkyBlockPlayer) c.player()), (click, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + if (d.getPrestigeLevel() < 3) { + p.sendMessage("§cThis requires Chocolate Factory IV!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (d.getCoachJackrabbitLevel() >= COACH_JACKRABBIT_MAX_LEVEL) { + p.sendMessage("§cCoach Jackrabbit has already taught you all that he can teach!"); + p.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (ChocolateFactoryHelper.purchaseUpgrade(p, ChocolateFactoryHelper.UpgradeType.COACH_JACKRABBIT)) { + p.sendMessage("§aUpgraded Coach Jackrabbit!"); + p.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(p); + } + c.session(State.class).refresh(); + }); + + // Slot 45: Chocolate Production info + layout.slot(45, (s, c) -> createProductionInfoItem((SkyBlockPlayer) c.player())); + + // Slot 49: Close + Components.close(layout, 49); + + // Slot 50: Hoppity's Collection + layout.slot(50, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData d = ChocolateFactoryHelper.getData(p); + + int rabbitsFound = d.getFoundRabbitCount(); + double percentage = (rabbitsFound / (double) TOTAL_RABBITS) * 100; + + List lore = new ArrayList<>(); + lore.add("§7Help §aHoppity §7find all of his §aChocolate"); + lore.add("§aRabbits §7during the §dHoppity's Hunt"); + lore.add("§d§7event!"); + lore.add(""); + lore.add("§7The more unique §aChocolate Rabbits"); + lore.add("§a§7that you find, the more your"); + lore.add("§7§6Chocolate Factory §7will produce!"); + lore.add(""); + lore.add("§7Rabbits Found: §e" + String.format("%.1f", percentage) + "§6%"); + lore.add("§2§l§m §f§l§m §r §e" + rabbitsFound + "§6/§e" + TOTAL_RABBITS); + lore.add(""); + lore.add("§eClick to view!"); + + return ItemStackCreator.getStackHead("§aHoppity's Collection", HOPPITY_TEXTURE, 1, lore); + }, (click, c) -> ((SkyBlockPlayer) c.player()).openView(new GUIHoppityCollection())); + + // Slot 51: Rabbit Hitman + layout.slot(51, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7§7Hire this private rabbit to hunt eggs"); + lore.add("§7for you, they will collect eggs you"); + lore.add("§7missed!"); + lore.add(""); + lore.add("§7Available eggs: §a0"); + lore.add("§7Purchased slots: §e0§7/§a28"); + lore.add(""); + lore.add("§eClick to view!"); + return ItemStackCreator.getStack("§cRabbit Hitman", Material.BOW, 1, lore); + }, (click, c) -> c.player().sendMessage("§7Opening Rabbit Hitman... (Coming Soon)")); + + // Slot 52: Chocolate Factory Ranking + layout.slot(52, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7§7You are §8#§b??? §7in all-time"); + lore.add("§7Chocolate."); + lore.add("§7§8You are in the top §e??%§8 of players!"); + return ItemStackCreator.getStack("§dChocolate Factory Ranking", Material.MILK_BUCKET, 1, lore); + }); + + // Slot 53: Chocolate Factory Milestones + layout.slot(53, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7Unlock special §aChocolate Rabbits §7by"); + lore.add("§7reaching all-time §6Chocolate"); + lore.add("§6§7milestones!"); + lore.add(""); + lore.add("§eClick to view!"); + return ItemStackCreator.getStack("§6Chocolate Factory Milestones", Material.LADDER, 1, lore); + }, (click, c) -> ((SkyBlockPlayer) c.player()).openView(new GUIChocolateFactoryMilestones())); + } + + private void setupEmployeeSlots(ViewLayout layout) { + for (int i = 0; i < EMPLOYEE_SLOTS.length; i++) { + int slot = EMPLOYEE_SLOTS[i]; + String employeeName = EMPLOYEE_NAMES[i]; + String employeeTexture = EMPLOYEE_TEXTURES[i]; + String employeeSubtitle = EMPLOYEE_SUBTITLES[i]; + + layout.slot(slot, (s, c) -> createEmployeeItem((SkyBlockPlayer) c.player(), employeeName, employeeTexture, employeeSubtitle), + (click, c) -> { + handleEmployeeClick((SkyBlockPlayer) c.player(), employeeName); + c.session(State.class).refresh(); + }); + } + } + + private void handleEmployeeClick(SkyBlockPlayer player, String employeeName) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (!ChocolateFactoryHelper.isEmployeeUnlocked(player, employeeName)) { + String prereq = ChocolateFactoryHelper.getEmployeePrerequisite(employeeName); + player.sendMessage("§cPromote §f" + prereq + " §cto §7[20§7] §fEmployee §cto unlock §f" + employeeName + "§c!"); + player.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + DatapointChocolateFactory.EmployeeData existingEmployee = data.getEmployees().get(employeeName); + + if (existingEmployee != null && existingEmployee.getLevel() >= EMPLOYEE_MAX_LEVEL) { + player.sendMessage("§b" + employeeName + " §ccannot ascend the corporate ladder any further!"); + player.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + + if (existingEmployee == null) { + long cost = ChocolateFactoryHelper.getEmployeeCost(employeeName, 1); + if (data.getChocolate() < cost) { + player.sendMessage("§c" + employeeName + " does not work at your §6Chocolate Factory §cyet!"); + player.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + return; + } + } + + if (ChocolateFactoryHelper.hireOrUpgradeEmployee(player, employeeName)) { + DatapointChocolateFactory.ChocolateFactoryData newData = ChocolateFactoryHelper.getData(player); + DatapointChocolateFactory.EmployeeData emp = newData.getEmployees().get(employeeName); + int level = emp != null ? emp.getLevel() : 1; + String rank = getEmployeeRank(level); + String rankColor = "§" + getEmployeeRankColor(level); + + player.sendMessage(rankColor + employeeName + " §7has been promoted to §7[" + level + "§7] " + rankColor + rank + "§7!"); + player.playSound(UPGRADE_SOUND); + } else { + sendNotEnoughChocolateFeedback(player); + } + } + + private ItemStack.Builder createEmployeeItem(SkyBlockPlayer player, String employeeName, String texture, String subtitle) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + boolean isUnlocked = ChocolateFactoryHelper.isEmployeeUnlocked(player, employeeName); + String prerequisite = ChocolateFactoryHelper.getEmployeePrerequisite(employeeName); + + if (!isUnlocked) { + List lore = new ArrayList<>(); + lore.add("§8§o" + subtitle); + lore.add(""); + lore.add("§7Promote §f" + prerequisite + " §7to §7[20§7]"); + lore.add("§fEmployee §7to unlock!"); + + return ItemStackCreator.getStack("§c" + employeeName, Material.GRAY_DYE, 1, lore); + } + + DatapointChocolateFactory.EmployeeData employee = data.getEmployees().get(employeeName); + double baseProduction = ChocolateFactoryHelper.getEmployeeBaseProduction(employeeName); + + if (employee == null) { + long cost = ChocolateFactoryHelper.getEmployeeCost(player, employeeName, 1); + boolean canAfford = data.getChocolate() >= cost; + + List lore = new ArrayList<>(); + lore.add("§8§o" + subtitle); + lore.add(""); + lore.add("§c" + employeeName + " §7does not work at"); + lore.add("§7your §6Chocolate Factory §7yet!"); + lore.add(""); + lore.add("§7Hire them and they will produce"); + lore.add("§6+" + String.format("%.0f", baseProduction) + " Chocolate §7per second!"); + lore.add(""); + lore.add("§7Cost: " + (canAfford ? "§6" : "§c") + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to hire!" : "§cYou don't have enough Chocolate!"); + + return ItemStackCreator.getStackHead("§c" + employeeName, texture, 1, lore); + } + + int level = employee.getLevel(); + long cost = ChocolateFactoryHelper.getEmployeeCost(player, employeeName, level + 1); + boolean canAfford = data.getChocolate() >= cost; + double currentProduction = baseProduction * level; + String rank = getEmployeeRank(level); + String rankColor = getEmployeeRankColor(level); + boolean isUnemployed = level < 1; + + List lore = new ArrayList<>(); + lore.add("§8§o" + subtitle); + lore.add(""); + lore.add("§7§" + rankColor + employeeName + " §7is " + (isUnemployed ? "" : "a ") + "§" + rankColor + rank + "§7. They"); + lore.add(rank.equals("Board Member") ? "§7are on the Board of Rabbits and" : "§7are working hard and"); + lore.add("§7produce §6+" + String.format("%.0f", currentProduction) + " Chocolate §7per second!"); + lore.add(""); + + if (level >= EMPLOYEE_MAX_LEVEL) { + lore.add("§7§" + rankColor + employeeName + " §ahas climbed as far as the"); + lore.add("§acorporate ladder will allow!"); + } else { + lore.add("§8§m-----------------"); + String action = isUnemployed ? "§a§lHIRE" : "§a§lPROMOTE"; + lore.add(action + " §8➜ §7[" + (level + 1) + "§7] §" + getEmployeeRankColor(level + 1) + getEmployeeRank(level + 1)); + lore.add(" §6+" + String.format("%.0f", baseProduction) + " Chocolate per second"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + String clickAction = isUnemployed ? "§eClick to hire!" : "§eClick to promote!"; + lore.add(canAfford ? clickAction : "§cYou don't have enough Chocolate!"); + } + + String title = isUnemployed + ? "§" + rankColor + employeeName + "§8 - §" + rankColor + rank + : "§" + rankColor + employeeName + "§8 - §7[" + level + "§7] §" + rankColor + rank; + return ItemStackCreator.getStackHead(title, texture, 1, lore); + } + + private ItemStack.Builder createRabbitBarnItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + int level = data.getRabbitBarnLevel(); + long cost = ChocolateFactoryHelper.getRabbitBarnCost(level); + boolean canAfford = data.getChocolate() >= cost; + + List lore = new ArrayList<>(); + lore.add("§7Your §aRabbit Barn §7can only hold so"); + lore.add("§7many §aChocolate Rabbits§7."); + lore.add(""); + lore.add("§7If you try collecting more unique"); + lore.add("§7rabbits than your barn can hold,"); + lore.add("§7they will be §ccrushed§7."); + lore.add(""); + lore.add("§7Your Barn: §a" + data.getEmployeeCount() + "§7/§a" + (data.getMaxRabbitSlots() + RABBIT_BARN_EXTRA_CAPACITY) + " Rabbits"); + if (level >= RABBIT_BARN_MAX_LEVEL) { + lore.add(""); + lore.add("§aYour Rabbit Barn is at maximum capacity!"); + } else { + lore.add("§8§m-----------------"); + lore.add("§a§lUPGRADE §8➜ §aRabbit Barn " + StringUtility.getAsRomanNumeral(level + 2)); + lore.add(" §a+2 Capacity"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to upgrade!" : "§cNot enough chocolate!"); + } + + return ItemStackCreator.getStack("§aRabbit Barn " + StringUtility.getAsRomanNumeral(level + 1), Material.OAK_FENCE, 1, lore); + } + + private ItemStack.Builder createHandBakedChocolateItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + int level = data.getHandBakedChocolateLevel(); + long cost = ChocolateFactoryHelper.getHandBakedChocolateCost(level); + boolean canAfford = data.getChocolate() >= cost; + + List lore = new ArrayList<>(); + lore.add("§7A good boss can get down in the"); + lore.add("§7trenches and help out their"); + lore.add("§7workforce. In exchange for some"); + lore.add("§7§6Chocolate§7, you can increase the"); + lore.add("§7amount of §6Chocolate §7that you"); + lore.add("§7produce each time you click!"); + lore.add(""); + lore.add("§7Chocolate Per Click: §6+" + data.getClickPower() + " Chocolate"); + lore.add(""); + if (level >= HAND_BAKED_MAX_LEVEL) { + lore.add("§aYou have reached the maximum"); + lore.add("§aamount of upgrades!"); + } else { + lore.add("§8§m-----------------"); + lore.add("§a§lUPGRADE §8➜ §dHand-Baked Chocolate " + StringUtility.getAsRomanNumeral(level + 2)); + lore.add(" §6+1 Chocolate Per Click"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to upgrade!" : "§cNot enough chocolate!"); + } + + return ItemStackCreator.getStack("§dHand-Baked Chocolate " + StringUtility.getAsRomanNumeral(level + 1), Material.COOKIE, 1, lore); + } + + private ItemStack.Builder createTimeTowerItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (data.getPrestigeLevel() < 1) { + List lore = new ArrayList<>(); + lore.add("§7What does it do? Nobody knows..."); + lore.add(""); + lore.add("§cChocolate Factory II"); + return ItemStackCreator.getStack("§c???", Material.GRAY_DYE, 1, lore); + } + + int level = data.getTimeTowerLevel(); + long cost = ChocolateFactoryHelper.getTimeTowerCost(level, data.getPrestigeLevel()); + boolean canAfford = data.getChocolate() >= cost; + boolean isActive = data.isTimeTowerActive(); + + List lore = new ArrayList<>(); + lore.add("§7When active, this ancient building"); + lore.add("§7increases the production of your"); + lore.add("§7§6Chocolate Factory §7by §6+" + String.format("%.1fx", (level > 0 ? 0.1 * level : 0.1)) + " §7for §a1h§7."); + lore.add(""); + if (isActive) { + long remaining = data.getTimeTowerActiveUntil() - System.currentTimeMillis(); + long minutes = remaining / MILLIS_PER_MINUTE; + long seconds = (remaining % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND; + lore.add("§7Status: §a§lACTIVE"); + lore.add("§7Time Remaining: §a" + minutes + "m " + seconds + "s"); + } else { + lore.add("§7Status: §c§lINACTIVE"); + } + lore.add(""); + lore.add("§7Charges: §a" + data.getTimeTowerCharges() + "§7/§a3"); + lore.add(""); + if (level >= TIME_TOWER_MAX_LEVEL) { + lore.add("§aThe Time Tower is maxed out!"); + } else { + lore.add("§8§m-----------------"); + lore.add("§a§lUPGRADE §8➜ §dTime Tower " + StringUtility.getAsRomanNumeral(level + 2)); + lore.add(" §6+0.1x Production Multiplier"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to upgrade!" : "§cNot enough chocolate!"); + } + if (data.getTimeTowerCharges() > 0 && !isActive) { + lore.add("§dRight-click to activate!"); + } + + return ItemStackCreator.getStack("§dTime Tower " + StringUtility.getAsRomanNumeral(level + 1), Material.CLOCK, 1, lore); + } + + private ItemStack.Builder createRabbitShrineItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (data.getPrestigeLevel() < 2) { + List lore = new ArrayList<>(); + lore.add("§7What does it do? Nobody knows..."); + lore.add(""); + lore.add("§cChocolate Factory III"); + return ItemStackCreator.getStack("§c???", Material.GRAY_DYE, 1, lore); + } + + int level = data.getRabbitShrineLevel(); + long cost = ChocolateFactoryHelper.getRabbitShrineCost(level); + boolean canAfford = data.getChocolate() >= cost; + int oddsBonus = level * 2; + + List lore = new ArrayList<>(); + lore.add("§7The §dRabbit Shrine §7increases the"); + lore.add("§7§dodds §7of finding §aChocolate Rabbits §7of"); + lore.add("§7higher rarity during §dHoppity's Hunt"); + lore.add("§d§7by §a" + oddsBonus + "%§7."); + lore.add(""); + if (level >= RABBIT_SHRINE_MAX_LEVEL) { + lore.add("§aYour Rabbit Shrine is at its maximum"); + lore.add("§alevel!"); + } else { + lore.add("§8§m-----------------"); + lore.add("§a§lUPGRADE §8➜ §dRabbit Shrine " + StringUtility.getAsRomanNumeral(level + 2)); + lore.add(" §a+2% Rare Rabbit Odds"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to upgrade!" : "§cNot enough chocolate!"); + } + + return ItemStackCreator.getStack("§dRabbit Shrine " + StringUtility.getAsRomanNumeral(level + 1), Material.RABBIT_FOOT, 1, lore); + } + + private ItemStack.Builder createCoachJackrabbitItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (data.getPrestigeLevel() < 3) { + List lore = new ArrayList<>(); + lore.add("§7What does it do? Nobody knows..."); + lore.add(""); + lore.add("§cChocolate Factory IV"); + return ItemStackCreator.getStack("§c???", Material.GRAY_DYE, 1, lore); + } + + int level = data.getCoachJackrabbitLevel(); + long cost = ChocolateFactoryHelper.getCoachJackrabbitCost(level); + boolean canAfford = data.getChocolate() >= cost; + double multiplierBonus = level * 0.01; + + List lore = new ArrayList<>(); + lore.add("§8§oPep talk pro!"); + lore.add(""); + lore.add("§7§dCoach Jackrabbit §7is a motivational"); + lore.add("§7speaker that is helping you reach"); + lore.add("§7your full potential by granting §6+" + String.format("%.2fx", multiplierBonus)); + lore.add("§6Chocolate §7per second!"); + lore.add(""); + if (level >= COACH_JACKRABBIT_MAX_LEVEL) { + lore.add("§aCoach Jackrabbit has already taught"); + lore.add("§ayou all that he can teach!"); + } else { + lore.add("§8§m-----------------"); + lore.add("§a§lUPGRADE §8➜ §dCoach Jackrabbit " + StringUtility.getAsRomanNumeral(level + 2)); + lore.add(" §6+0.01x Production Multiplier"); + lore.add(""); + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + lore.add(""); + lore.add(canAfford ? "§eClick to upgrade!" : "§cNot enough chocolate!"); + } + + return ItemStackCreator.getStackHead("§dCoach Jackrabbit " + StringUtility.getAsRomanNumeral(level + 1), COACH_JACKRABBIT_TEXTURE, 1, lore); + } + + private ItemStack.Builder createProductionInfoItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + double employeeProduction = 0; + for (DatapointChocolateFactory.EmployeeData employee : data.getEmployees().values()) { + employeeProduction += employee.getProductionPerSecond(); + } + + // Calculate bonuses from Hoppity's Collection + int rabbitChocolateBonus = 0; + double rabbitMultiplierBonus = 0; + for (String rabbitName : data.getFoundRabbits()) { + try { + ChocolateRabbit rabbit = ChocolateRabbit.valueOf(rabbitName); + rabbitChocolateBonus += rabbit.getChocolateBonus(); + rabbitMultiplierBonus += rabbit.getMultiplierBonus(); + } catch (IllegalArgumentException ignored) { + } + } + + double baseProduction = employeeProduction + rabbitChocolateBonus; + double totalMultiplier = data.getShrineMultiplier() * data.getTimeTowerMultiplier() * data.getCoachMultiplier() + rabbitMultiplierBonus; + + List lore = new ArrayList<>(); + lore.add("§6" + String.format("%.2f", data.getChocolatePerSecond()) + " Chocolate §8per second"); + lore.add(""); + lore.add("§7Base Chocolate: §6" + String.format("%.0f", baseProduction) + " §8per second"); + lore.add(" §6+" + String.format("%.0f", employeeProduction) + " §8(Rabbit Employees§8)"); + if (rabbitChocolateBonus > 0) { + lore.add(" §6+" + rabbitChocolateBonus + " §8(Hoppity's Collection§8)"); + } + lore.add(""); + lore.add("§7Total Multiplier: §6" + String.format("%.3fx", totalMultiplier)); + lore.add(" §6+1x §8(Base Multiplier)"); + if (rabbitMultiplierBonus > 0) { + lore.add(" §6+" + String.format("%.3fx", rabbitMultiplierBonus) + " §8(Hoppity's Collection§8)"); + } + if (data.getRabbitShrineLevel() > 0) { + lore.add(" §6+" + String.format("%.1fx", data.getShrineMultiplier() - 1.0) + " §8(Rabbit Shrine)"); + } + if (data.getCoachJackrabbitLevel() > 0) { + lore.add(" §6+" + String.format("%.2fx", data.getCoachMultiplier() - 1.0) + " §8(Coach Jackrabbit)"); + } + if (data.isTimeTowerActive()) { + lore.add(" §6+" + String.format("%.1fx", data.getTimeTowerMultiplier() - 1.0) + " §8(Time Tower)"); + } + + return ItemStackCreator.getStack("§6Chocolate Production", Material.COCOA_BEANS, 1, lore); + } + + private String getEmployeeRank(int level) { + if (level >= EMPLOYEE_MAX_LEVEL) return "Board Member"; + if (level >= 200) return "Executive"; + if (level >= 180) return "Director"; + if (level >= 140) return "Manager"; + if (level >= 120) return "Assistant"; + if (level >= 20) return "Employee"; + if (level >= 1) return "Intern"; + return "Unemployed"; + } + + private String getEmployeeRankColor(int level) { + if (level >= EMPLOYEE_MAX_LEVEL) return "b"; + if (level >= 200) return "d"; + if (level >= 180) return "6"; + if (level >= 140) return "5"; + if (level >= 120) return "9"; + if (level >= 20) return "a"; + if (level >= 1) return "f"; + return "c"; + } + + private void sendNotEnoughChocolateFeedback(SkyBlockPlayer player) { + player.sendMessage(NOT_ENOUGH_CHOCOLATE_MESSAGE); + player.playSound(NOT_ENOUGH_CHOCOLATE_SOUND); + } + +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactoryMilestones.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactoryMilestones.java new file mode 100644 index 000000000..73e24ffe3 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateFactoryMilestones.java @@ -0,0 +1,172 @@ +package net.swofty.type.skyblockgeneric.gui.inventories; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.*; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateMilestone; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIChocolateFactoryMilestones implements StatefulView { + private static final int PROGRESS_BAR_SEGMENTS = 25; + private static final double PERCENT_PER_SEGMENT = 100.0 / PROGRESS_BAR_SEGMENTS; + + private static final int[] MILESTONE_SLOTS = { + 27, 18, 9, 0, 1, 2, 11, 20, 29, 30, 31, 22, + 13, 4, 5, 6, 15, 24, 33, 34, 35, 26, 17, 8 + }; + + public record State() {} + + @Override + public State initialState() { + return new State(); + } + + @Override + public ViewConfiguration configuration() { + return ViewConfiguration.withString((state, ctx) -> "Chocolate Factory Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, State state, ViewContext ctx) { + Components.fill(layout); + + // Set milestone items + for (ChocolateMilestone milestone : ChocolateMilestone.values()) { + int slot = MILESTONE_SLOTS[milestone.getNumber() - 1]; + layout.slot(slot, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + long allTimeChocolate = data.getChocolateAllTime(); + + if (milestone.isUnlocked(allTimeChocolate)) { + return createUnlockedMilestoneItem(milestone); + } else { + return createLockedMilestoneItem(milestone, allTimeChocolate); + } + }); + } + + // Go Back button (slot 48) + Components.back(layout, 48, ctx); + + // Close button (slot 49) + Components.close(layout, 49); + } + + private ItemStack.Builder createUnlockedMilestoneItem(ChocolateMilestone milestone) { + List lore = new ArrayList<>(); + String formattedReq = formatRequirement(milestone.getRequiredChocolate()); + + lore.add("§7Reach §6" + formattedReq + " Chocolate §7all-time to"); + lore.add("§7unlock this special §aChocolate Rabbit§7!"); + lore.add(""); + lore.add("§7Milestone " + milestone.getRomanNumeral() + " Reward"); + lore.add("§" + milestone.getColorCode() + milestone.getRabbitName()); + lore.add(""); + addRewardLore(lore, milestone); + lore.add(""); + lore.add("§a§lUNLOCKED"); + + return ItemStackCreator.getStackHead( + getMilestoneName(milestone), + milestone.getTextureId(), + 1, + lore + ); + } + + private ItemStack.Builder createLockedMilestoneItem(ChocolateMilestone milestone, long allTimeChocolate) { + List lore = new ArrayList<>(); + String formattedReq = formatRequirement(milestone.getRequiredChocolate()); + String formattedCurrent = formatRequirement(allTimeChocolate); + double progress = milestone.getProgress(allTimeChocolate); + + lore.add("§7Reach §6" + formattedReq + " Chocolate §7all-time to"); + lore.add("§7unlock this special §aChocolate Rabbit§7!"); + lore.add(""); + lore.add("§7Progress to Milestone " + milestone.getRomanNumeral() + ": §b" + String.format("%.0f", progress) + "%"); + lore.add(createProgressBar(progress) + " §b" + formattedCurrent + "§3/§b" + formattedReq); + lore.add(""); + lore.add("§7Milestone " + milestone.getRomanNumeral() + " Reward"); + lore.add("§" + milestone.getColorCode() + milestone.getRabbitName()); + lore.add(""); + addRewardLore(lore, milestone); + lore.add(""); + lore.add("§cRequires " + formattedReq + " all-time Chocolate!"); + + return ItemStackCreator.getStack( + getMilestoneName(milestone), + milestone.getGlassPaneMaterial(), + 1, + lore + ); + } + + private String createProgressBar(double progress) { + int filled = (int) (progress / PERCENT_PER_SEGMENT); + int empty = PROGRESS_BAR_SEGMENTS - filled; + + StringBuilder bar = new StringBuilder("§3§l§m"); + for (int i = 0; i < filled; i++) { + bar.append(" "); + } + bar.append("§f§l§m"); + for (int i = 0; i < empty; i++) { + bar.append(" "); + } + bar.append("§r"); + + return bar.toString(); + } + + private String formatRequirement(long amount) { + if (amount >= 1_000_000_000_000L) { + double val = amount / 1_000_000_000_000.0; + return val == (long) val ? String.format("%.0fT", val) : String.format("%.1fT", val); + } else if (amount >= 1_000_000_000L) { + double val = amount / 1_000_000_000.0; + return val == (long) val ? String.format("%.0fB", val) : String.format("%.1fB", val); + } else if (amount >= 1_000_000L) { + double val = amount / 1_000_000.0; + return val == (long) val ? String.format("%.0fM", val) : String.format("%.1fM", val); + } else if (amount >= 1_000L) { + double val = amount / 1_000.0; + return val == (long) val ? String.format("%.0fk", val) : String.format("%.1fk", val); + } + return String.valueOf(amount); + } + + private String getOrdinal(int number) { + String[] suffixes = {"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}; + if (number % 100 >= 11 && number % 100 <= 13) { + return number + "th"; + } + return number + suffixes[number % 10]; + } + + private String getMilestoneName(ChocolateMilestone milestone) { + return "§" + milestone.getColorCode() + getOrdinal(milestone.getNumber()) + " Chocolate Milestone"; + } + + private void addRewardLore(List lore, ChocolateMilestone milestone) { + if (milestone.getChocolateBonus() > 0) { + lore.add("§7Grants §6+" + milestone.getChocolateBonus() + " Chocolate §7and §6" + + String.format("%.3fx", milestone.getMultiplierBonus())); + lore.add("§6Chocolate §7per second to your"); + lore.add("§7§6Chocolate Factory§7."); + return; + } + + lore.add("§7Grants §6+" + String.format("%.2fx", milestone.getMultiplierBonus()) + " Chocolate §7per second"); + lore.add("§7to your §6Chocolate Factory§7."); + } +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShop.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShop.java new file mode 100644 index 000000000..ef91160b5 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShop.java @@ -0,0 +1,551 @@ +package net.swofty.type.skyblockgeneric.gui.inventories; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.*; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIChocolateShop implements StatefulView { + // Texture IDs (extracted from skull texture URLs) + private static final String SUPREME_CHOCOLATE_BAR_TEXTURE = "254b7f3f2a6f0d1c2c054678128ec2322619aefff0f450c390d6a41b5950302e"; + private static final String EGGLOCATOR_TEXTURE = "14a7ff9a10fdae14446499ef3bc1df13b7888d6cd2e311ccab51b8352c6093b4"; + private static final String NIBBLE_CHOCOLATE_STICK_TEXTURE = "888188d62908af6e114f73a109e15ac7f1faded39abd6a2054034ec5cc70c727"; + private static final String SMOOTH_CHOCOLATE_BAR_TEXTURE = "a9372efd2ca1a6c6dfc066f1ec83f9456575c3850a0e7d01109c4f1af300ba8"; + private static final String RICH_CHOCOLATE_CHUNK_TEXTURE = "6f942717364c0fecf7ad11bac8cd98dd7ad4dbd72e3d3ce2b57eb48713824ff"; + private static final String GANACHE_CHOCOLATE_SLAB_TEXTURE = "f89512331edfdc27cb7d4e80f3e0db460d05caf66c7c1c42e0e712130a9b690"; + private static final String PRESTIGE_CHOCOLATE_REALM_TEXTURE = "af19ceeabf2ecb020610b8aabc9299264fa670048c010c9699ce687fc9bf351e"; + private static final String DARK_CACAO_TRUFFLE_TEXTURE = "db9db373cadbec1912a9ab386d31ceb3e0cd4d6a64f222426588a3b2eb31ed29"; + private static final String CHOCOLATE_DYE_TEXTURE = "a15e7208539306f65d68df9be6c3124c48027e307739fc8dc35526febd643c21"; + private static final String BARN_SKIN_TEXTURE = "af90da40c557af4ac01d39b6733e204c74ae9fee8c2bc40be1fd4f28f837d52"; + private static final String CHOCOLATE_SYRINGE_TEXTURE = "7dcb67a72c01f3ca75da846f957ffed6417f0c45ad814fb3e340c317cf316718"; + private static final String CHOCO_RABBIT_MINION_TEXTURE = "9a815398e7da89b1bc08f646cafc8e7b813da0be0eec0cce6d3eff5207801026"; + private static final String ZORROS_CAPE_TEXTURE = "81f7226a927558d069a6ae343b4e089fbd60fc6037190097c7713208e988faae"; + private static final String FISH_CHOCOLAT_TEXTURE = "422b0e5faa97ca109cd45f1fba2d84ca2b9b601de50b47f4add2d835aa360f78"; + private static final String HOT_CHOCOLATE_MIXIN_TEXTURE = "4fde9c68bc5a89f01a5e5203eecc5367d494d55a47c81e6b1d689a0c4488b6e"; + + public record State() {} + + @Override + public State initialState() { + return new State(); + } + + @Override + public ViewConfiguration configuration() { + return ViewConfiguration.withString((state, ctx) -> "Chocolate Shop", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, State state, ViewContext ctx) { + Components.fill(layout); + + // Slot 10: Supreme Chocolate Bar + layout.slot(10, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7Bring §63,000 §7of these to §5Carrolyn §7in"); + lore.add("§5Scarleton §7on the §cCrimson Isle §7to"); + lore.add("§7permanently gain §c+5\u2764 Health §7and"); + lore.add("§6+12\u2618 Cocoa Beans Fortune§7."); + lore.add(""); + lore.add("§a§lUNCOMMON"); + lore.add(""); + addCostLore(lore, (SkyBlockPlayer) c.player(), 2_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§6500 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + lore.add("§eRight-click for more trading options!"); + return ItemStackCreator.getStackHead("§aSupreme Chocolate Bar", SUPREME_CHOCOLATE_BAR_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 2_000_000L, "§aSupreme Chocolate Bar", 0, c)); + + // Slot 11: Egglocator + layout.slot(11, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7Uses the magic of §aHoppity §7to"); + lore.add("§7uncover hidden §aChocolate Rabbit"); + lore.add("§aEggs§7."); + lore.add(""); + lore.add("§6Ability: Egglocator §e§lRIGHT CLICK"); + lore.add("§7Points towards the nearest unclaimed"); + lore.add("§aChocolate Rabbit Egg§7!"); + lore.add("§8Cooldown: §a5s"); + lore.add(""); + lore.add("§7Only works during §dHoppity's Hunt§7."); + lore.add(""); + lore.add("§f§lCOMMON"); + lore.add(""); + addCostLore(lore, (SkyBlockPlayer) c.player(), 7_500_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§fEgglocator", EGGLOCATOR_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 7_500_000L, "§fEgglocator", 0, c)); + + // Slot 12: Nibble Chocolate Stick + layout.slot(12, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 1) { + return createLockedItem("§cChocolate Factory II."); + } + List lore = new ArrayList<>(); + lore.add("§7Grants §a+2% §7chance to find a"); + lore.add("§aChocolate Rabbit §7that you haven't"); + lore.add("§7found yet and grants §6+10 Chocolate"); + lore.add("§7per second."); + lore.add(""); + lore.add("§8§oA delightful treat from the Factory."); + lore.add("§8§oIts crisp taste sparks joy with"); + lore.add("§8§oevery bite."); + lore.add(""); + lore.add("§f§lCOMMON ACCESSORY"); + lore.add(""); + addCostLore(lore, p, 250_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§fNibble Chocolate Stick", NIBBLE_CHOCOLATE_STICK_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 250_000_000L, "§fNibble Chocolate Stick", 1, c)); + + // Slot 13: Smooth Chocolate Bar + layout.slot(13, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 1) { + return createLockedItem("§cChocolate Factory II."); + } + List lore = new ArrayList<>(); + lore.add("§7Grants §a+4% §7chance to find a"); + lore.add("§aChocolate Rabbit §7that you haven't"); + lore.add("§7found yet and grants §6+20 Chocolate"); + lore.add("§7per second."); + lore.add(""); + lore.add("§8§oCrafted in the Factory, its"); + lore.add("§8§osmoothness melts hearts and tastes"); + lore.add("§8§olike a sweet escape."); + lore.add(""); + lore.add("§a§lUNCOMMON ACCESSORY"); + lore.add(""); + addCostLore(lore, p, 1_000_000_000L); + lore.add("§fNibble Chocolate Stick"); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§aSmooth Chocolate Bar", SMOOTH_CHOCOLATE_BAR_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 1_000_000_000L, "§aSmooth Chocolate Bar", 1, c)); + + // Slot 14: Rich Chocolate Chunk + layout.slot(14, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 2) { + return createLockedItem("§cChocolate Factory III."); + } + List lore = new ArrayList<>(); + lore.add("§7Grants §a+6% §7chance to find a"); + lore.add("§aChocolate Rabbit §7that you haven't"); + lore.add("§7found yet and grants §6+30 Chocolate"); + lore.add("§7per second."); + lore.add(""); + lore.add("§8§oFrom the Factory's secret"); + lore.add("§8§oreserves, its rich flavor is a deep"); + lore.add("§8§odive into indulgence."); + lore.add(""); + lore.add("§9§lRARE ACCESSORY"); + lore.add(""); + addCostLore(lore, p, 2_000_000_000L); + lore.add("§aSmooth Chocolate Bar"); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§9Rich Chocolate Chunk", RICH_CHOCOLATE_CHUNK_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 2_000_000_000L, "§9Rich Chocolate Chunk", 2, c)); + + // Slot 15: Ganache Chocolate Slab + layout.slot(15, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 2) { + return createLockedItem("§cChocolate Factory III."); + } + List lore = new ArrayList<>(); + lore.add("§7Grants §a+8% §7chance to find a"); + lore.add("§aChocolate Rabbit §7that you haven't"); + lore.add("§7found yet and grants §6+40 Chocolate"); + lore.add("§7per second."); + lore.add(""); + lore.add("§8§oA Factory masterpiece - its divine"); + lore.add("§8§otaste transcends reality, offering a"); + lore.add("§8§oheavenly escape."); + lore.add(""); + lore.add("§5§lEPIC ACCESSORY"); + lore.add(""); + addCostLore(lore, p, 3_000_000_000L); + lore.add("§9Rich Chocolate Chunk"); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§5Ganache Chocolate Slab", GANACHE_CHOCOLATE_SLAB_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 3_000_000_000L, "§5Ganache Chocolate Slab", 2, c)); + + // Slot 16: Prestige Chocolate Realm + layout.slot(16, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 3) { + return createLockedItem("§cChocolate Factory IV."); + } + List lore = new ArrayList<>(); + lore.add("§7Grants §a+10% §7chance to find a"); + lore.add("§aChocolate Rabbit §7that you haven't"); + lore.add("§7found yet and grants §6+50 Chocolate"); + lore.add("§7per second."); + lore.add(""); + lore.add("§8§oThe Factory's pinnacle creation - its"); + lore.add("§8§oepic taste shatters expectations,"); + lore.add("§8§ooffering a taste of utopia."); + lore.add(""); + lore.add("§6§lLEGENDARY ACCESSORY"); + lore.add(""); + addCostLore(lore, p, 4_500_000_000L); + lore.add("§5Ganache Chocolate Slab"); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§6Prestige Chocolate Realm", PRESTIGE_CHOCOLATE_REALM_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 4_500_000_000L, "§6Prestige Chocolate Realm", 3, c)); + + // Slot 19: Dark Cacao Truffle + layout.slot(19, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 3) { + return createLockedItem("§cChocolate Factory IV."); + } + List lore = new ArrayList<>(); + lore.add("§7Consume to boost your §6\u2618 Global"); + lore.add("§6Fortune §7for §a60m§7."); + lore.add(""); + lore.add("§7Keep this item in your inventory to"); + lore.add("§7increase the bonus up to §6+30§6\u2618"); + lore.add("§6Global Fortune§7, at which point the"); + lore.add("§7item §c§oevolves§7!"); + lore.add(""); + lore.add("§7Current Bonus: §6+0§6\u2618 Global Fortune"); + lore.add(""); + lore.add("§9§lRARE"); + lore.add(""); + addCostLore(lore, p, 2_500_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§62 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§9Dark Cacao Truffle", DARK_CACAO_TRUFFLE_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 2_500_000_000L, "§9Dark Cacao Truffle", 3, c)); + + // Slot 20: Chocolate Dye + layout.slot(20, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 5) { + return createLockedItem("§cChocolate Factory VI."); + } + List lore = new ArrayList<>(); + lore.add("§8Combinable in Anvil"); + lore.add(""); + lore.add("§7Changes the color of an armor piece"); + lore.add("§7to §6#7B3F00§7!"); + lore.add(""); + lore.add("§5§lEPIC DYE"); + lore.add(""); + addCostLore(lore, p, 40_000_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§6Chocolate Dye", CHOCOLATE_DYE_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 40_000_000_000L, "§6Chocolate Dye", 5, c)); + + // Slot 21: Chocolate Factory Barn Skin + layout.slot(21, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 4) { + return createLockedItem("§cChocolate Factory V."); + } + List lore = new ArrayList<>(); + lore.add("§7Consume this item to unlock the"); + lore.add("§6Chocolate Factory Barn Skin §7on §aThe"); + lore.add("§aGarden§7!"); + lore.add(""); + lore.add("§eClick to consume!"); + lore.add(""); + lore.add("§6§lLEGENDARY COSMETIC"); + lore.add(""); + addCostLore(lore, p, 7_000_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§6Chocolate Factory Barn Skin", BARN_SKIN_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 7_000_000_000L, "§6Chocolate Factory Barn Skin", 4, c)); + + // Slot 22: Chocolate Syringe + layout.slot(22, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 4) { + return createLockedItem("§cChocolate Factory V."); + } + List lore = new ArrayList<>(); + lore.add("§7Use at §bKat §7to upgrade §eRabbit Pets §7to"); + lore.add("§dMythic §7rarity."); + lore.add(""); + lore.add("§d§lMYTHIC"); + lore.add(""); + addCostLore(lore, p, 10_000_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§dChocolate Syringe", CHOCOLATE_SYRINGE_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 10_000_000_000L, "§dChocolate Syringe", 4, c)); + + // Slot 23: Choco Rabbit Minion Skin + layout.slot(23, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 4) { + return createLockedItem("§cChocolate Factory V."); + } + List lore = new ArrayList<>(); + lore.add("§7This Minion skin changes your"); + lore.add("§7minion's appearance to a §eChoco"); + lore.add("§eRabbit§7."); + lore.add(""); + lore.add("§7You can place this item in any minion"); + lore.add("§7of your choice!"); + lore.add(""); + lore.add("§5§lEPIC COSMETIC"); + lore.add(""); + addCostLore(lore, p, 2_500_000_000L); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§62 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§5Choco Rabbit Minion Skin", CHOCO_RABBIT_MINION_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 2_500_000_000L, "§5Choco Rabbit Minion Skin", 4, c)); + + // Slot 24: Zorro's Cape + layout.slot(24, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7Strength: §c+10"); + lore.add("§7Ferocity: §c+2"); + lore.add("§7Farming Fortune: §6+10"); + lore.add("§7Farming Wisdom: §3+1"); + lore.add(""); + lore.add("§7The stats of this Cape §adouble "); + lore.add("§7during §eJacob's Farming Contest§7."); + lore.add("§7Additionally, you have a §a20% §7chance"); + lore.add("§7to obtain an extra medal from"); + lore.add("§7contests."); + lore.add(""); + lore.add("§8§oNot all Rabbits wear capes."); + lore.add(""); + lore.add("§8This item can be reforged!"); + lore.add("§4\u2763 §cRequires §dZorro §cin Hoppity's Collection§c."); + lore.add("§6§lLEGENDARY CLOAK"); + lore.add(""); + addCostLore(lore, (SkyBlockPlayer) c.player(), 20_000_000_000L); + lore.add(""); + lore.add("§cNot unlocked!"); + return ItemStackCreator.getStackHead("§6Zorro's Cape", ZORROS_CAPE_TEXTURE, 1, lore); + }); + + // Slot 25: Fish Chocolat à la Vapeur + layout.slot(25, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 5) { + return createLockedItem("§cChocolate Factory VI."); + } + List lore = new ArrayList<>(); + lore.add("§7Give this dish to §aHoppity §7to obtain his"); + lore.add("§aAbiphone Contact§7."); + lore.add(""); + lore.add("§8§oSavory fish with a chocolate twist."); + lore.add("§8§oMwah! C'est magnifique, no?"); + lore.add(""); + lore.add("§5§lEPIC"); + lore.add(""); + addCostLore(lore, p, 50_000_000_000L); + lore.add("§cRabbit the Fish"); + lore.add(""); + lore.add("§7Annual Stock §8Year 471"); + lore.add("§61 §7remaining"); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§5Fish Chocolat \u00e0 la Vapeur", FISH_CHOCOLAT_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 50_000_000_000L, "§5Fish Chocolat \u00e0 la Vapeur", 5, c)); + + // Slot 28: Hot Chocolate Mixin + layout.slot(28, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 5) { + List lore = new ArrayList<>(); + lore.add("§8Brewing Ingredient"); + lore.add(""); + lore.add("§7Mixins provide a buff that can be"); + lore.add("§7added to §cGod Potions §7in a brewing"); + lore.add("§7stand and lasts for the full duration."); + lore.add(""); + lore.add("§7Gain §d+15\u2663 Pet Luck §7and §6+0.05x"); + lore.add("§6Chocolate §7per second."); + lore.add(""); + lore.add("§7Duration: §a36h 0m"); + lore.add(""); + lore.add("§7The duration of Mixins can be stacked!"); + lore.add(""); + lore.add("§eRight-click to consume!§8"); + lore.add("§8(Requires active Booster Cookie)"); + lore.add(""); + lore.add("§4\u2763 §cRequires §cChocolate Factory VI§c."); + lore.add("§9§lRARE"); + lore.add(""); + addCostLore(lore, p, 1_500_000_000L); + lore.add(""); + lore.add("§cNot unlocked!"); + return ItemStackCreator.getStackHead("§9Hot Chocolate Mixin", HOT_CHOCOLATE_MIXIN_TEXTURE, 1, lore); + } + List lore = new ArrayList<>(); + lore.add("§8Brewing Ingredient"); + lore.add(""); + lore.add("§7Mixins provide a buff that can be"); + lore.add("§7added to §cGod Potions §7in a brewing"); + lore.add("§7stand and lasts for the full duration."); + lore.add(""); + lore.add("§7Gain §d+15\u2663 Pet Luck §7and §6+0.05x"); + lore.add("§6Chocolate §7per second."); + lore.add(""); + lore.add("§7Duration: §a36h 0m"); + lore.add(""); + lore.add("§7The duration of Mixins can be stacked!"); + lore.add(""); + lore.add("§eRight-click to consume!§8"); + lore.add("§8(Requires active Booster Cookie)"); + lore.add(""); + lore.add("§9§lRARE"); + lore.add(""); + addCostLore(lore, p, 1_500_000_000L); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStackHead("§9Hot Chocolate Mixin", HOT_CHOCOLATE_MIXIN_TEXTURE, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 1_500_000_000L, "§9Hot Chocolate Mixin", 5, c)); + + // Slot 29: Chocolate Fortune + layout.slot(29, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + int prestige = ChocolateFactoryHelper.getData(p).getPrestigeLevel(); + if (prestige < 5) { + List lore = new ArrayList<>(); + lore.add("§7Permanently gain §6+1\u2618 Cocoa Beans"); + lore.add("§6Fortune §7per tier."); + lore.add(""); + addCostLore(lore, p, 2_000_000_000L); + lore.add(""); + lore.add("§cChocolate Factory VI."); + return ItemStackCreator.getStack("§eChocolate Fortune", Material.COCOA_BEANS, 1, lore); + } + List lore = new ArrayList<>(); + lore.add("§7Permanently gain §6+1\u2618 Cocoa Beans"); + lore.add("§6Fortune §7per tier."); + lore.add(""); + addCostLore(lore, p, 2_000_000_000L); + lore.add(""); + lore.add("§eClick to trade!"); + return ItemStackCreator.getStack("§eChocolate Fortune", Material.COCOA_BEANS, 1, lore); + }, (click, c) -> handlePurchase((SkyBlockPlayer) c.player(), 2_000_000_000L, "§eChocolate Fortune", 5, c)); + + // Slot 48: Go Back + Components.back(layout, 48, ctx); + + // Slot 49: Close + Components.close(layout, 49); + + // Slot 50: Chocolate Shop Milestones + layout.slot(50, (s, c) -> { + SkyBlockPlayer p = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(p); + + List lore = new ArrayList<>(); + lore.add("§7Unlock special §aChocolate Rabbits §7by"); + lore.add("§7spending §6Chocolate §7in the §6Chocolate"); + lore.add("§6Shop§7."); + lore.add(""); + lore.add("§7Chocolate Spent: §6" + ChocolateFactoryHelper.formatChocolate(data.getTotalChocolateSpent())); + lore.add(""); + lore.add("§eClick to view!"); + return ItemStackCreator.getStack("§6Chocolate Shop Milestones", Material.LADDER, 1, lore); + }, (click, c) -> ((SkyBlockPlayer) c.player()).openView(new GUIChocolateShopMilestones())); + } + + private void addCostLore(List lore, SkyBlockPlayer player, long cost) { + lore.add("§7Cost"); + lore.add("§6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate"); + } + + private ItemStack.Builder createLockedItem(String requirement) { + List lore = new ArrayList<>(); + lore.add("§7???"); + lore.add(""); + lore.add(requirement); + return ItemStackCreator.getStack("§c???", Material.GRAY_DYE, 1, lore); + } + + private void handlePurchase(SkyBlockPlayer player, long cost, String itemName, int requiredPrestige, ViewContext c) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + + if (data.getPrestigeLevel() < requiredPrestige) { + player.sendMessage("§cYou don't meet the requirements for this item!"); + return; + } + + if (data.getChocolate() >= cost) { + data.removeChocolate(cost); + data.addChocolateSpent(cost); + player.getChocolateFactoryDatapoint().setValue(data); + player.sendMessage("§aPurchased " + itemName + " §afor §6" + ChocolateFactoryHelper.formatChocolate(cost) + " Chocolate§a!"); + c.session(State.class).refresh(); + } else { + player.sendMessage("§cYou don't have enough Chocolate!"); + } + } + +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShopMilestones.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShopMilestones.java new file mode 100644 index 000000000..cfac31475 --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIChocolateShopMilestones.java @@ -0,0 +1,171 @@ +package net.swofty.type.skyblockgeneric.gui.inventories; + +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.*; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateShopMilestone; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class GUIChocolateShopMilestones implements StatefulView { + private static final int[] MILESTONE_SLOTS = { + 27, 18, 9, 0, 1, 2, 11, 20, 29, 30, 31, 22, + 13, 4, 5, 6, 15, 24, 33, 34, 35, 26, 17, 8 + }; + + public record State() {} + + @Override + public State initialState() { + return new State(); + } + + @Override + public ViewConfiguration configuration() { + return ViewConfiguration.withString((state, ctx) -> "Chocolate Shop Milestones", InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, State state, ViewContext ctx) { + Components.fill(layout); + + // Set milestone items + for (ChocolateShopMilestone milestone : ChocolateShopMilestone.values()) { + int slot = MILESTONE_SLOTS[milestone.getNumber() - 1]; + layout.slot(slot, (s, c) -> { + SkyBlockPlayer player = (SkyBlockPlayer) c.player(); + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + long totalSpent = data.getTotalChocolateSpent(); + + if (milestone.isUnlocked(totalSpent)) { + return createUnlockedMilestoneItem(milestone); + } else { + return createLockedMilestoneItem(milestone, totalSpent); + } + }); + } + + // Go Back button (slot 48) + Components.back(layout, 48, ctx); + + // Close button (slot 49) + Components.close(layout, 49); + } + + private ItemStack.Builder createUnlockedMilestoneItem(ChocolateShopMilestone milestone) { + List lore = new ArrayList<>(); + String formattedReq = formatRequirement(milestone.getRequiredSpent()); + + lore.add("§7Spend §6" + formattedReq + " Chocolate §7in the shop"); + lore.add("§7to unlock this special §aChocolate Rabbit§7!"); + lore.add(""); + lore.add("§7Milestone " + milestone.getRomanNumeral() + " Reward"); + lore.add("§" + milestone.getColorCode() + milestone.getRabbitName()); + lore.add(""); + + if (milestone.getChocolateBonus() > 0) { + lore.add("§7Grants §6+" + milestone.getChocolateBonus() + " Chocolate §7and §6" + + String.format("%.3fx", milestone.getMultiplierBonus())); + lore.add("§6Chocolate §7per second to your"); + lore.add("§7§6Chocolate Factory§7."); + } else { + lore.add("§7Grants §6+" + String.format("%.2fx", milestone.getMultiplierBonus()) + " Chocolate §7per second"); + lore.add("§7to your §6Chocolate Factory§7."); + } + lore.add(""); + lore.add("§a§lUNLOCKED"); + + return ItemStackCreator.getStackHead( + "§" + milestone.getColorCode() + getOrdinal(milestone.getNumber()) + " Shop Milestone", + milestone.getTextureId(), + 1, + lore + ); + } + + private ItemStack.Builder createLockedMilestoneItem(ChocolateShopMilestone milestone, long totalSpent) { + List lore = new ArrayList<>(); + String formattedReq = formatRequirement(milestone.getRequiredSpent()); + String formattedCurrent = formatRequirement(totalSpent); + double progress = milestone.getProgress(totalSpent); + + lore.add("§7Spend §6" + formattedReq + " Chocolate §7in the shop"); + lore.add("§7to unlock this special §aChocolate Rabbit§7!"); + lore.add(""); + lore.add("§7Progress to Milestone " + milestone.getRomanNumeral() + ": §b" + String.format("%.0f", progress) + "%"); + lore.add(createProgressBar(progress) + " §b" + formattedCurrent + "§3/§b" + formattedReq); + lore.add(""); + lore.add("§7Milestone " + milestone.getRomanNumeral() + " Reward"); + lore.add("§" + milestone.getColorCode() + milestone.getRabbitName()); + lore.add(""); + + if (milestone.getChocolateBonus() > 0) { + lore.add("§7Grants §6+" + milestone.getChocolateBonus() + " Chocolate §7and §6" + + String.format("%.3fx", milestone.getMultiplierBonus())); + lore.add("§6Chocolate §7per second to your"); + lore.add("§7§6Chocolate Factory§7."); + } else { + lore.add("§7Grants §6+" + String.format("%.2fx", milestone.getMultiplierBonus()) + " Chocolate §7per second"); + lore.add("§7to your §6Chocolate Factory§7."); + } + lore.add(""); + lore.add("§cRequires spending " + formattedReq + " Chocolate!"); + + return ItemStackCreator.getStack( + "§" + milestone.getColorCode() + getOrdinal(milestone.getNumber()) + " Shop Milestone", + milestone.getGlassPaneMaterial(), + 1, + lore + ); + } + + private String createProgressBar(double progress) { + int filled = (int) (progress / 4); + int empty = 25 - filled; + + StringBuilder bar = new StringBuilder("§3§l§m"); + for (int i = 0; i < filled; i++) { + bar.append(" "); + } + bar.append("§f§l§m"); + for (int i = 0; i < empty; i++) { + bar.append(" "); + } + bar.append("§r"); + + return bar.toString(); + } + + private String formatRequirement(long amount) { + if (amount >= 1_000_000_000_000L) { + double val = amount / 1_000_000_000_000.0; + return val == (long) val ? String.format("%.0fT", val) : String.format("%.1fT", val); + } else if (amount >= 1_000_000_000L) { + double val = amount / 1_000_000_000.0; + return val == (long) val ? String.format("%.0fB", val) : String.format("%.1fB", val); + } else if (amount >= 1_000_000L) { + double val = amount / 1_000_000.0; + return val == (long) val ? String.format("%.0fM", val) : String.format("%.1fM", val); + } else if (amount >= 1_000L) { + double val = amount / 1_000.0; + return val == (long) val ? String.format("%.0fk", val) : String.format("%.1fk", val); + } + return String.valueOf(amount); + } + + private String getOrdinal(int number) { + String[] suffixes = {"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}; + if (number % 100 >= 11 && number % 100 <= 13) { + return number + "th"; + } + return number + suffixes[number % 10]; + } + +} diff --git a/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIHoppityCollection.java b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIHoppityCollection.java new file mode 100644 index 000000000..16ef917eb --- /dev/null +++ b/type.skyblockgeneric/src/main/java/net/swofty/type/skyblockgeneric/gui/inventories/GUIHoppityCollection.java @@ -0,0 +1,367 @@ +package net.swofty.type.skyblockgeneric.gui.inventories; + +import lombok.Getter; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.swofty.type.generic.gui.inventory.ItemStackCreator; +import net.swofty.type.generic.gui.v2.*; +import net.swofty.type.generic.gui.v2.context.ViewContext; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateFactoryHelper; +import net.swofty.type.skyblockgeneric.chocolatefactory.ChocolateRabbit; +import net.swofty.type.skyblockgeneric.data.datapoints.DatapointChocolateFactory; +import net.swofty.type.skyblockgeneric.user.SkyBlockPlayer; + +import java.util.*; +import java.util.stream.Collectors; + +public class GUIHoppityCollection implements StatefulView { + private static final String HOPPITY_TEXTURE = "b79e7f3341b672d9de6564cbaca052a6a723ea466a2e66af35ba1ba855f0d692"; + private static final String LOCATION_TEXTURE = "d7cc6687423d0570d556ac53e0676cb563bbdd9717cd8269bdebed6f6d4e7bf8"; + private static final String FOUND_RABBIT_TEXTURE = HOPPITY_TEXTURE; + + private static final int[] RABBIT_SLOTS = { + 10, 11, 12, 13, 14, 15, 16, + 19, 20, 21, 22, 23, 24, 25, + 28, 29, 30, 31, 32, 33, 34, + 37, 38, 39, 40, 41, 42, 43 + }; + + private static final int RABBITS_PER_PAGE = RABBIT_SLOTS.length; + private static final int TOTAL_RABBITS = ChocolateRabbit.values().length; + private static final int PROGRESS_BAR_SEGMENTS = 25; + private static final double PERCENT_PER_PROGRESS_SEGMENT = 100.0 / PROGRESS_BAR_SEGMENTS; + + private static final int[] BORDER_SLOTS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 17, 18, 26, 27, 35, 36, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53 + }; + + public record State(int page, SortType sortType, FilterType filterType) {} + + @Override + public State initialState() { + return new State(1, SortType.A_TO_Z, FilterType.NONE); + } + + @Override + public ViewConfiguration configuration() { + return ViewConfiguration.withString((state, ctx) -> { + int totalRabbits = ChocolateRabbit.values().length; + int totalPages = Math.max(1, (int) Math.ceil(totalRabbits / (double) RABBITS_PER_PAGE)); + return "(" + state.page() + "/" + totalPages + ") Hoppity's Collection"; + }, InventoryType.CHEST_6_ROW); + } + + @Override + public void layout(ViewLayout layout, State state, ViewContext ctx) { + SkyBlockPlayer player = (SkyBlockPlayer) ctx.player(); + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + Set foundRabbits = data.getFoundRabbits(); + + List rabbits = getFilteredAndSortedRabbits(foundRabbits, state.sortType(), state.filterType()); + int totalPages = Math.max(1, (int) Math.ceil(rabbits.size() / (double) RABBITS_PER_PAGE)); + + // Fill border + for (int slot : BORDER_SLOTS) { + layout.slot(slot, (s, c) -> ItemStackCreator.createNamedItemStack(Material.BLACK_STAINED_GLASS_PANE, " ")); + } + + // Slot 4: Collection info + layout.slot(4, (s, c) -> createCollectionInfoItem((SkyBlockPlayer) c.player())); + + // Rabbit items + int startIndex = (state.page() - 1) * RABBITS_PER_PAGE; + for (int i = 0; i < RABBIT_SLOTS.length; i++) { + int rabbitIndex = startIndex + i; + int slot = RABBIT_SLOTS[i]; + + if (rabbitIndex < rabbits.size()) { + ChocolateRabbit rabbit = rabbits.get(rabbitIndex); + layout.slot(slot, (s, c) -> { + boolean found = foundRabbits.contains(rabbit.name()); + return createRabbitItem(rabbit, found); + }); + } else { + layout.slot(slot, (s, c) -> ItemStack.AIR.builder()); + } + } + + // Slot 47: Rabbit Locations + layout.slot(47, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§7Each rabbit has a specific location"); + lore.add("§7on a specific island where it can be"); + lore.add("§7found, just for fun."); + lore.add(""); + lore.add("§7The §9Hotspot §7of a Rabbit means that"); + lore.add("§7this season they have a §a50% §7higher"); + lore.add("§7chance to be found on this specific"); + lore.add("§7island."); + lore.add(""); + lore.add("§6Resident §7rabbits however, can §cONLY"); + lore.add("§7be found on their respective islands."); + lore.add(""); + lore.add("§7Currently selected: §aAll Rabbits"); + lore.add(""); + lore.add("§bRight-click to go backwards!"); + lore.add("§eClick to cycle!"); + + return ItemStackCreator.getStackHead("§9Rabbit Locations", LOCATION_TEXTURE, 1, lore); + }, (click, c) -> c.player().sendMessage("§7Rabbit Locations filter coming soon!")); + + // Slot 48: Go Back + Components.back(layout, 48, ctx); + + // Slot 49: Close + Components.close(layout, 49); + + // Slot 50: Sort + layout.slot(50, (s, c) -> { + List lore = new ArrayList<>(); + lore.add(""); + for (SortType type : SortType.values()) { + if (type == s.sortType()) { + lore.add("§b▶ " + type.getDisplayName()); + } else { + lore.add("§7 " + type.getDisplayName()); + } + } + lore.add(""); + lore.add("§bRight-click to go backwards!"); + lore.add("§eClick to switch sort!"); + + return ItemStackCreator.getStack("§aSort", Material.HOPPER, 1, lore); + }, (click, c) -> { + boolean isRightClick = click.click() instanceof Click.Right; + SortType newSort = isRightClick ? state.sortType().previous() : state.sortType().next(); + c.session(State.class).setState(new State(1, newSort, state.filterType())); + }); + + // Slot 51: Filter + layout.slot(51, (s, c) -> { + List lore = new ArrayList<>(); + lore.add(""); + for (FilterType type : FilterType.values()) { + if (type == s.filterType()) { + lore.add("§a▶ " + type.getDisplayName()); + } else { + lore.add("§7 " + type.getDisplayName()); + } + } + lore.add(""); + lore.add("§bRight-click to go backwards!"); + lore.add("§eClick to switch!"); + + return ItemStackCreator.getStack("§aFilter", Material.ENDER_EYE, 1, lore); + }, (click, c) -> { + boolean isRightClick = click.click() instanceof Click.Right; + FilterType newFilter = isRightClick ? state.filterType().previous() : state.filterType().next(); + c.session(State.class).setState(new State(1, state.sortType(), newFilter)); + }); + + // Slot 53: Next Page + if (state.page() < totalPages) { + layout.slot(53, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§ePage " + (s.page() + 1)); + return ItemStackCreator.getStack("§aNext Page", Material.ARROW, 1, lore); + }, (click, c) -> c.session(State.class).setState(new State(state.page() + 1, state.sortType(), state.filterType()))); + } + + // Slot 45: Previous Page + if (state.page() > 1) { + layout.slot(45, (s, c) -> { + List lore = new ArrayList<>(); + lore.add("§ePage " + (s.page() - 1)); + return ItemStackCreator.getStack("§aPrevious Page", Material.ARROW, 1, lore); + }, (click, c) -> c.session(State.class).setState(new State(state.page() - 1, state.sortType(), state.filterType()))); + } + } + + private ItemStack.Builder createCollectionInfoItem(SkyBlockPlayer player) { + DatapointChocolateFactory.ChocolateFactoryData data = ChocolateFactoryHelper.getData(player); + Set foundRabbits = data.getFoundRabbits(); + int found = foundRabbits.size(); + double percentage = (found / (double) TOTAL_RABBITS) * 100; + + int totalChocolate = 0; + double totalMultiplier = 0; + for (String rabbitName : foundRabbits) { + try { + ChocolateRabbit rabbit = ChocolateRabbit.valueOf(rabbitName); + totalChocolate += rabbit.getChocolateBonus(); + totalMultiplier += rabbit.getMultiplierBonus(); + } catch (IllegalArgumentException ignored) { + } + } + + List lore = new ArrayList<>(); + lore.add("§7Help §aHoppity §7find all of his §aChocolate"); + lore.add("§aRabbits §7during the §dHoppity's Hunt"); + lore.add("§7event!"); + lore.add(""); + lore.add("§7The more unique §aChocolate Rabbits"); + lore.add("§7that you find, the more your"); + lore.add("§6Chocolate Factory §7will produce!"); + lore.add(""); + lore.add("§7Finding duplicate Rabbits grants"); + lore.add("§a+10% §7extra §6Chocolate §7per duplicate,"); + lore.add("§7up to §a+100%§7!"); + lore.add(""); + lore.add("§7Rabbits Found: §e" + String.format("%.1f", percentage) + "§6%"); + lore.add(createProgressBar(percentage) + " §e" + found + "§6/§e" + TOTAL_RABBITS); + lore.add(""); + if (totalChocolate > 0) { + lore.add("§6+" + totalChocolate + " Chocolate per second"); + } + if (totalMultiplier > 0) { + lore.add("§6+" + String.format("%.3fx", totalMultiplier) + " Chocolate Multiplier"); + } + + return ItemStackCreator.getStackHead("§aHoppity's Collection", HOPPITY_TEXTURE, 1, lore); + } + + private ItemStack.Builder createRabbitItem(ChocolateRabbit rabbit, boolean found) { + List lore = new ArrayList<>(); + + if (rabbit.getRarity() == ChocolateRabbit.Rarity.LEGENDARY || + rabbit.getRarity() == ChocolateRabbit.Rarity.DIVINE || + rabbit.getRarity() == ChocolateRabbit.Rarity.MYTHIC) { + lore.add("§7Grants §6+" + String.format("%.2fx", rabbit.getMultiplierBonus()) + " Chocolate §7per second"); + lore.add("§7to your §6Chocolate Factory§7."); + } else { + lore.add("§7Grants §6+" + rabbit.getChocolateBonus() + " Chocolate §7and §6" + + String.format("%.3fx", rabbit.getMultiplierBonus())); + lore.add("§6Chocolate §7per second to your"); + lore.add("§6Chocolate Factory§7."); + } + lore.add(""); + + if (rabbit.getObtainMethod() != null) { + lore.add("§7" + rabbit.getObtainMethod()); + lore.add(""); + } + + if (rabbit.getRequirement() != null) { + lore.add("§c✖ §7Requirement"); + lore.add("§7" + rabbit.getRequirement()); + lore.add(""); + if (!found) { + lore.add("§8You cannot find this rabbit until you"); + lore.add("§8meet the requirement!"); + lore.add(""); + } + } + + if (!found) { + lore.add("§8You have not found this rabbit yet!"); + } + + if (rabbit.getLocation() != null) { + lore.add(""); + lore.add(rabbit.getResidentLabel()); + } + lore.add(""); + lore.add(rabbit.getRarity().getFormattedName()); + + if (found) { + return ItemStackCreator.getStackHead(rabbit.getFormattedName(), + FOUND_RABBIT_TEXTURE, 1, lore); + } else { + return ItemStackCreator.getStack(rabbit.getFormattedName(), Material.GRAY_DYE, 1, lore); + } + } + + private List getFilteredAndSortedRabbits(Set foundRabbits, SortType sortType, FilterType filterType) { + List rabbits = Arrays.stream(ChocolateRabbit.values()) + .filter(rabbit -> { + boolean found = foundRabbits.contains(rabbit.name()); + return switch (filterType) { + case NONE -> true; + case FOUND -> found; + case NOT_FOUND -> !found; + case HAS_REQUIREMENT -> rabbit.getRequirement() != null; + case NO_REQUIREMENT -> rabbit.getRequirement() == null; + }; + }) + .collect(Collectors.toList()); + + switch (sortType) { + case A_TO_Z -> rabbits.sort(Comparator.comparing(ChocolateRabbit::getDisplayName)); + case Z_TO_A -> rabbits.sort(Comparator.comparing(ChocolateRabbit::getDisplayName).reversed()); + case HIGHEST_RARITY -> rabbits.sort(Comparator.comparing((ChocolateRabbit r) -> r.getRarity().ordinal()).reversed()); + case LOWEST_RARITY -> rabbits.sort(Comparator.comparing(r -> r.getRarity().ordinal())); + } + + return rabbits; + } + + private String createProgressBar(double progress) { + int filled = (int) (progress / PERCENT_PER_PROGRESS_SEGMENT); + int empty = PROGRESS_BAR_SEGMENTS - filled; + + StringBuilder bar = new StringBuilder("§2§l§m"); + for (int i = 0; i < filled; i++) { + bar.append(" "); + } + bar.append("§f§l§m"); + for (int i = 0; i < empty; i++) { + bar.append(" "); + } + bar.append("§r"); + + return bar.toString(); + } + + @Getter + public enum SortType { + A_TO_Z("A to Z"), + Z_TO_A("Z to A"), + HIGHEST_RARITY("Highest Rarity"), + LOWEST_RARITY("Lowest Rarity"); + + private final String displayName; + + SortType(String displayName) { + this.displayName = displayName; + } + + public SortType next() { + SortType[] all = values(); + return all[(ordinal() + 1) % all.length]; + } + + public SortType previous() { + SortType[] all = values(); + return all[(ordinal() - 1 + all.length) % all.length]; + } + } + + @Getter + public enum FilterType { + NONE("None"), + FOUND("Rabbits Found"), + NOT_FOUND("Rabbits Not Found"), + HAS_REQUIREMENT("Has Requirement"), + NO_REQUIREMENT("No Requirement"); + + private final String displayName; + + FilterType(String displayName) { + this.displayName = displayName; + } + + public FilterType next() { + FilterType[] all = values(); + return all[(ordinal() + 1) % all.length]; + } + + public FilterType previous() { + FilterType[] all = values(); + return all[(ordinal() - 1 + all.length) % all.length]; + } + } +} 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..fd092a3e9 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 @@ -148,6 +148,14 @@ public DatapointArcheryPractice.ArcheryPracticeData getArcheryPracticeData() { return getSkyblockDataHandler().get(SkyBlockDataHandler.Data.ARCHERY_PRACTICE, DatapointArcheryPractice.class).getValue(); } + public DatapointChocolateFactory.ChocolateFactoryData getChocolateFactoryData() { + return getSkyblockDataHandler().get(SkyBlockDataHandler.Data.CHOCOLATE_FACTORY, DatapointChocolateFactory.class).getValue(); + } + + public DatapointChocolateFactory getChocolateFactoryDatapoint() { + return getSkyblockDataHandler().get(SkyBlockDataHandler.Data.CHOCOLATE_FACTORY, DatapointChocolateFactory.class); + } + public String getFullDisplayName(SkyBlockEmblems.SkyBlockEmblem displayEmblem, String levelColor) { DatapointSkyBlockExperience.PlayerSkyBlockExperience experience = getSkyBlockExperience();