From 90d7bfee8802064cc6bb278fbe2bca1e42b02555 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Sat, 9 Dec 2023 10:36:42 -0800 Subject: [PATCH] Implement drops from cocoons/world bosses --- .../java/emu/lunarcore/GameConstants.java | 1 + .../java/emu/lunarcore/data/GameData.java | 5 + .../emu/lunarcore/data/excel/CocoonExcel.java | 1 + .../data/excel/MappingInfoExcel.java | 166 ++++++++++++++++++ .../emu/lunarcore/game/battle/Battle.java | 5 + .../lunarcore/game/battle/BattleService.java | 21 +++ .../emu/lunarcore/game/drops/DropMap.java | 11 ++ .../emu/lunarcore/game/drops/DropParam.java | 99 +++++++++++ .../emu/lunarcore/game/drops/DropService.java | 53 ++++-- src/main/java/emu/lunarcore/util/Utils.java | 6 + 10 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java create mode 100644 src/main/java/emu/lunarcore/game/drops/DropMap.java create mode 100644 src/main/java/emu/lunarcore/game/drops/DropParam.java diff --git a/src/main/java/emu/lunarcore/GameConstants.java b/src/main/java/emu/lunarcore/GameConstants.java index 4f38a5f..a071a21 100644 --- a/src/main/java/emu/lunarcore/GameConstants.java +++ b/src/main/java/emu/lunarcore/GameConstants.java @@ -20,6 +20,7 @@ public class GameConstants { public static final int MAX_AVATARS_IN_TEAM = 4; public static final int DEFAULT_TEAMS = 6; public static final int MAX_MP = 5; // Client doesnt like more than 5 + public static final int FARM_ELEMENT_STAMINA_COST = 30; // Chat/Social public static final int MAX_FRIENDSHIPS = 100; diff --git a/src/main/java/emu/lunarcore/data/GameData.java b/src/main/java/emu/lunarcore/data/GameData.java index b203b0f..771f69e 100644 --- a/src/main/java/emu/lunarcore/data/GameData.java +++ b/src/main/java/emu/lunarcore/data/GameData.java @@ -61,6 +61,7 @@ public class GameData { private static Int2ObjectMap equipmentPromotionExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap mazeBuffExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap cocoonExcelMap = new Int2ObjectOpenHashMap<>(); + private static Int2ObjectMap mappingInfoExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap monsterDropExcelMap = new Int2ObjectOpenHashMap<>(); private static Int2ObjectMap playerLevelExcelMap = new Int2ObjectOpenHashMap<>(); @@ -219,6 +220,10 @@ public class GameData { return cocoonExcelMap.get((cocoonId << 8) + worldLevel); } + public static MappingInfoExcel getMappingInfoExcel(int mappingInfoId, int worldLevel) { + return mappingInfoExcelMap.get((mappingInfoId << 8) + worldLevel); + } + public static MonsterDropExcel getMonsterDropExcel(int monsterNpcId, int worldLevel) { return monsterDropExcelMap.get((monsterNpcId << 4) + worldLevel); } diff --git a/src/main/java/emu/lunarcore/data/excel/CocoonExcel.java b/src/main/java/emu/lunarcore/data/excel/CocoonExcel.java index 86826a9..621257b 100644 --- a/src/main/java/emu/lunarcore/data/excel/CocoonExcel.java +++ b/src/main/java/emu/lunarcore/data/excel/CocoonExcel.java @@ -10,6 +10,7 @@ import lombok.Getter; @ResourceType(name = {"CocoonConfig.json"}) public class CocoonExcel extends GameResource { private int ID; + private int MappingInfoID; private int WorldLevel; private int PropID; private int StaminaCost; diff --git a/src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java b/src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java new file mode 100644 index 0000000..2180506 --- /dev/null +++ b/src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java @@ -0,0 +1,166 @@ +package emu.lunarcore.data.excel; + +import java.util.ArrayList; +import java.util.List; + +import emu.lunarcore.GameConstants; +import emu.lunarcore.data.GameData; +import emu.lunarcore.data.GameResource; +import emu.lunarcore.data.ResourceType; +import emu.lunarcore.data.ResourceType.LoadPriority; +import emu.lunarcore.data.common.ItemParam; +import emu.lunarcore.game.drops.DropParam; +import emu.lunarcore.game.enums.ItemMainType; +import emu.lunarcore.game.enums.ItemRarity; +import emu.lunarcore.game.enums.ItemSubType; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; + +@Getter +@ResourceType(name = {"MappingInfo.json"}, loadPriority = LoadPriority.LOW) +public class MappingInfoExcel extends GameResource { + private int ID; + private int WorldLevel; + private String FarmType; // is enum + private List DisplayItemList; + + private transient List dropList; + + @Override + public int getId() { + return (ID << 8) + WorldLevel; + } + + @Override + public void onLoad() { + // Temp way to pre-calculate drop list + this.dropList = new ArrayList<>(this.getDisplayItemList().size()); + + var equipmentDrops = new IntArrayList(); + var relicDrops = new Int2ObjectOpenHashMap(); + + for (var itemParam : this.getDisplayItemList()) { + // Add item param if the amount is already set in the excel + if (itemParam.getCount() > 0) { + dropList.add(new DropParam(itemParam.getId(), itemParam.getCount())); + continue; + } + + // Multiplier. TODO drop rate is not correct + int multiplier = 1; + if (FarmType == null) { + // Skip + } else if (FarmType.equals("RELIC")) { + multiplier = 4; + } else if (FarmType.equals("COCOON2")) { + multiplier = 3; + } else if (FarmType.equals("ELEMENT")) { + multiplier = 3; + } + + // Random credits + if (itemParam.getId() == GameConstants.MATERIAL_COIN_ID) { + // TODO drop rate is not correct + DropParam drop = new DropParam(itemParam.getId(), 0); + drop.setMinCount((50 + (this.getWorldLevel() * 10)) * multiplier); + drop.setMaxCount((100 + (this.getWorldLevel() * 10)) * multiplier); + dropList.add(drop); + continue; + } + + // Get item excel + ItemExcel itemExcel = GameData.getItemExcelMap().get(itemParam.getId()); + if (itemExcel == null) continue; + + // Hacky way of calculating drops + if (itemExcel.getItemSubType() == ItemSubType.RelicSetShowOnly) { + // Get relic base id from relic display id + int baseRelicId = (itemParam.getId() / 10) % 1000; + int baseRarity = itemParam.getId() % 10; + + // Add relics from the set + int relicStart = 20001 + (baseRarity * 10000) + (baseRelicId * 10); + int relicEnd = relicStart + 3; + for (;relicStart < relicEnd; relicStart++) { + ItemExcel relicExcel = GameData.getItemExcelMap().get(relicStart); + if (relicExcel == null) break; + + relicDrops + .computeIfAbsent(baseRarity, r -> new IntArrayList()) + .add(relicStart); + } + } else if (itemExcel.getItemMainType() == ItemMainType.Material) { + // Calculate amount to drop by purpose level + DropParam drop = switch (itemExcel.getPurposeType()) { + // Avatar exp. TODO drop rate is not correct + case 1 -> new DropParam(itemParam.getId(), 1); + // Boss materials + case 2 -> new DropParam(itemParam.getId(), this.getWorldLevel()); + // Trace materials. TODO drop rate is not correct + case 3 -> { + var dropInfo = new DropParam(itemParam.getId(), 1); + + if (itemExcel.getRarity() == ItemRarity.VeryRare) { + dropInfo.setChance((this.getWorldLevel() - 3) * 75); + } + + yield dropInfo; + } + // Boss Trace materials. TODO drop rate is not correct + case 4 -> new DropParam(itemParam.getId(), (this.getWorldLevel() * 0.5) + 0.5); + // Lightcone exp. TODO drop rate is not correct + case 5 -> new DropParam(itemParam.getId(), 1); + // Lucent afterglow + case 11 -> new DropParam(itemParam.getId(), 4 + this.getWorldLevel()); + // Unknown + default -> null; + }; + + if (drop != null) { + dropList.add(drop); + } + } else if (itemExcel.getItemMainType() == ItemMainType.Equipment) { + // Lightcones + equipmentDrops.add(itemParam.getId()); + } + } + + // Add equipment drops + if (equipmentDrops.size() > 0) { + DropParam drop = new DropParam(); + drop.getItems().addAll(equipmentDrops); + drop.setCount(1); + drop.setChance((this.getWorldLevel() * 10) + 40); + dropList.add(drop); + } + + // Add relic drops + if (relicDrops.size() > 0) { + for (var entry : relicDrops.int2ObjectEntrySet()) { + // Add items to drop param + DropParam drop = new DropParam(); + drop.getItems().addAll(entry.getValue()); + + // Set count by rarity + double amount = switch (entry.getIntKey()) { + case 4: + yield (this.getWorldLevel() * 0.5) - 0.5; + case 3: + yield (this.getWorldLevel() * 0.5) + (this.getWorldLevel() == 2 ? 1.0 : 0); + case 2: + yield (6 - this.getWorldLevel()) + 0.5 - (this.getWorldLevel() == 1 ? 3.75 : 0); + default: + yield this.getWorldLevel() == 1 ? 6 : 2; + }; + + // Set amount + if (amount > 0) { + drop.setCount(amount); + dropList.add(drop); + } + } + } + } +} diff --git a/src/main/java/emu/lunarcore/game/battle/Battle.java b/src/main/java/emu/lunarcore/game/battle/Battle.java index eed297b..d74333e 100644 --- a/src/main/java/emu/lunarcore/game/battle/Battle.java +++ b/src/main/java/emu/lunarcore/game/battle/Battle.java @@ -40,6 +40,11 @@ public class Battle { @Setter private int levelOverride; @Setter private int roundsLimit; + // Used for calculating cocoon/farm element drops + @Setter private int mappingInfoId; + @Setter private int worldLevel; + @Setter private int cocoonWave; + private Battle(Player player, PlayerLineup lineup) { this.id = player.getNextBattleId(); this.player = player; diff --git a/src/main/java/emu/lunarcore/game/battle/BattleService.java b/src/main/java/emu/lunarcore/game/battle/BattleService.java index 3e671a4..586c613 100644 --- a/src/main/java/emu/lunarcore/game/battle/BattleService.java +++ b/src/main/java/emu/lunarcore/game/battle/BattleService.java @@ -119,8 +119,16 @@ public class BattleService extends BaseGameService { // Add npc monsters for (var monster : monsters) { + // Add npc monster battle.getNpcMonsters().add(monster); + // Check farm element + if (monster.getFarmElementId() != 0) { + battle.setMappingInfoId(monster.getFarmElementId()); + battle.setWorldLevel(monster.getWorldLevel()); + battle.setStaminaCost(GameConstants.FARM_ELEMENT_STAMINA_COST); + } + // Handle monster buffs // TODO handle multiple waves properly monster.applyBuffs(battle); @@ -213,6 +221,9 @@ public class BattleService extends BaseGameService { // Build battle from cocoon data Battle battle = new Battle(player, player.getLineupManager().getCurrentLineup(), stages); + battle.setMappingInfoId(cocoonExcel.getMappingInfoID()); + battle.setCocoonWave(wave); + battle.setWorldLevel(worldLevel); battle.setStaminaCost(cost); player.setBattle(battle); @@ -338,8 +349,18 @@ public class BattleService extends BaseGameService { // Create new battle for player Battle battle = new Battle(player, player.getCurrentLineup(), stage); + battle.setStaminaCost(GameConstants.FARM_ELEMENT_STAMINA_COST); player.setBattle(battle); + // Get mapping info id + int mappingInfoId = ((stageId / 10) % 100) + 1100; + int mappingInfoLevel = stageId % 10; + var mappingInfoExcel = GameData.getMappingInfoExcel(mappingInfoId, mappingInfoLevel); + if (mappingInfoExcel != null && mappingInfoExcel.getFarmType() != null && mappingInfoExcel.getFarmType().equals("ELEMENT")) { + battle.setMappingInfoId(mappingInfoId); + battle.setWorldLevel(mappingInfoLevel); + } + // Send packet player.sendPacket(new PacketReEnterLastElementStageScRsp(battle)); } diff --git a/src/main/java/emu/lunarcore/game/drops/DropMap.java b/src/main/java/emu/lunarcore/game/drops/DropMap.java new file mode 100644 index 0000000..4e29fab --- /dev/null +++ b/src/main/java/emu/lunarcore/game/drops/DropMap.java @@ -0,0 +1,11 @@ +package emu.lunarcore.game.drops; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + +public class DropMap extends Int2IntOpenHashMap { + private static final long serialVersionUID = -4186524272780523459L; + + public FastEntrySet entries() { + return this.int2IntEntrySet(); + } +} diff --git a/src/main/java/emu/lunarcore/game/drops/DropParam.java b/src/main/java/emu/lunarcore/game/drops/DropParam.java new file mode 100644 index 0000000..e1855b8 --- /dev/null +++ b/src/main/java/emu/lunarcore/game/drops/DropParam.java @@ -0,0 +1,99 @@ +package emu.lunarcore.game.drops; + +import emu.lunarcore.data.GameData; +import emu.lunarcore.data.excel.ItemExcel; +import emu.lunarcore.util.Utils; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class DropParam { + @Setter(AccessLevel.NONE) + private IntList items; + + private int minCount; + private int maxCount; + private int chance; + + public DropParam() { + this.items = new IntArrayList(); + this.chance = 1000; + } + + public DropParam(int itemId, int count) { + this(); + this.getItems().add(itemId); + this.setCount(count); + } + + public DropParam(int itemId, double count) { + this(); + this.getItems().add(itemId); + this.setCount(count); + } + + public void setCount(int count) { + this.minCount = count; + this.maxCount = count; + } + + public void setCount(double count) { + if (count % 1 == 0) { + this.setCount((int) count); + } else { + this.setMaxCount((int) Math.ceil(count)); + this.setMinCount((int) Math.floor(count)); + } + } + + public int generateItemId() { + if (this.items == null || this.items.size() == 0) { + return 0; + } + + if (this.items.size() == 1) { + return this.items.getInt(0); + } + + return Utils.randomElement(this.items); + } + + public int generateCount() { + if (this.maxCount > this.minCount) { + return Utils.randomRange(this.minCount, this.maxCount); + } + return this.maxCount; + } + + public void roll(DropMap drops) { + // Check drop chance + if (this.chance < 1000) { + int random = Utils.randomRange(0, 999); + if (random > this.chance) { + return; + } + } + + // Get count + int count = generateCount(); + + // Generate item(s) + while (count > 0) { + int itemId = generateItemId(); + + ItemExcel excel = GameData.getItemExcelMap().get(itemId); + if (excel == null) break; + + if (excel.isEquippable()) { + drops.addTo(itemId, 1); + count--; + } else { + drops.addTo(itemId, count); + count -= count; + } + } + } +} diff --git a/src/main/java/emu/lunarcore/game/drops/DropService.java b/src/main/java/emu/lunarcore/game/drops/DropService.java index 35d4d45..9894f59 100644 --- a/src/main/java/emu/lunarcore/game/drops/DropService.java +++ b/src/main/java/emu/lunarcore/game/drops/DropService.java @@ -6,13 +6,13 @@ import java.util.List; import emu.lunarcore.GameConstants; import emu.lunarcore.data.GameData; import emu.lunarcore.data.common.ItemParam; +import emu.lunarcore.data.excel.ItemExcel; import emu.lunarcore.game.battle.Battle; import emu.lunarcore.game.inventory.GameItem; import emu.lunarcore.game.scene.entity.EntityMonster; import emu.lunarcore.server.game.BaseGameService; import emu.lunarcore.server.game.GameServer; import emu.lunarcore.util.Utils; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; public class DropService extends BaseGameService { @@ -20,15 +20,12 @@ public class DropService extends BaseGameService { super(server); } + // TODO this isnt the right way drops are calculated on the official server... but its good enough for now public void calculateDrops(Battle battle) { - // TODO this isnt the right way drops are calculated on the official server... but its good enough for now - if (battle.getNpcMonsters().size() == 0) { - return; - } + // Setup drop map + var dropMap = new DropMap(); - var dropMap = new Int2IntOpenHashMap(); - - // Get drops from monsters + // Calculate drops from monsters for (EntityMonster monster : battle.getNpcMonsters()) { var dropExcel = GameData.getMonsterDropExcel(monster.getExcel().getId(), monster.getWorldLevel()); if (dropExcel == null || dropExcel.getDisplayItemList() == null) { @@ -43,22 +40,50 @@ public class DropService extends BaseGameService { count = dropExcel.getAvatarExpReward(); } - dropMap.put(id, count + dropMap.get(id)); + dropMap.addTo(id, count); } } - for (var entry : dropMap.int2IntEntrySet()) { + // Mapping info + if (battle.getMappingInfoId() > 0) { + var mapInfoExcel = GameData.getMappingInfoExcel(battle.getMappingInfoId(), battle.getWorldLevel()); + if (mapInfoExcel != null) { + int rolls = Math.max(battle.getCocoonWave(), 1); + for (var dropParam : mapInfoExcel.getDropList()) { + for (int i = 0; i < rolls; i++) { + dropParam.roll(dropMap); + } + } + } + } + + // Sanity check + if (dropMap.size() == 0) { + return; + } + + // Create drops + for (var entry : dropMap.entries()) { if (entry.getIntValue() <= 0) { continue; } // Create item and add it to player - GameItem item = new GameItem(entry.getIntKey(), entry.getIntValue()); - - if (battle.getPlayer().getInventory().addItem(item)) { - battle.getDrops().add(item); + ItemExcel excel = GameData.getItemExcelMap().get(entry.getIntKey()); + if (excel == null) continue; + + // Add item + if (excel.isEquippable()) { + for (int i = 0; i < entry.getIntValue(); i++) { + battle.getDrops().add(new GameItem(excel, 1)); + } + } else { + battle.getDrops().add(new GameItem(excel, entry.getIntValue())); } } + + // Add to inventory + battle.getPlayer().getInventory().addItems(battle.getDrops()); } // TODO filler diff --git a/src/main/java/emu/lunarcore/util/Utils.java b/src/main/java/emu/lunarcore/util/Utils.java index 0f0b6ec..25a45e7 100644 --- a/src/main/java/emu/lunarcore/util/Utils.java +++ b/src/main/java/emu/lunarcore/util/Utils.java @@ -8,6 +8,8 @@ import java.util.Base64; import java.util.List; import java.util.concurrent.ThreadLocalRandom; +import it.unimi.dsi.fastutil.ints.IntList; + public class Utils { private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); @@ -175,6 +177,10 @@ public class Utils { public static T randomElement(List list) { return list.get(ThreadLocalRandom.current().nextInt(0, list.size())); } + + public static int randomElement(IntList list) { + return list.getInt(ThreadLocalRandom.current().nextInt(0, list.size())); + } /** * Checks if an integer array contains a value