mirror of
https://github.com/Melledy/LunarCore.git
synced 2025-12-16 07:14:58 +01:00
Implement drops from cocoons/world bosses
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -61,6 +61,7 @@ public class GameData {
|
||||
private static Int2ObjectMap<EquipmentPromotionExcel> equipmentPromotionExcelMap = new Int2ObjectOpenHashMap<>();
|
||||
private static Int2ObjectMap<MazeBuffExcel> mazeBuffExcelMap = new Int2ObjectOpenHashMap<>();
|
||||
private static Int2ObjectMap<CocoonExcel> cocoonExcelMap = new Int2ObjectOpenHashMap<>();
|
||||
private static Int2ObjectMap<MappingInfoExcel> mappingInfoExcelMap = new Int2ObjectOpenHashMap<>();
|
||||
private static Int2ObjectMap<MonsterDropExcel> monsterDropExcelMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static Int2ObjectMap<PlayerLevelExcel> 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
166
src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java
Normal file
166
src/main/java/emu/lunarcore/data/excel/MappingInfoExcel.java
Normal file
@@ -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<ItemParam> DisplayItemList;
|
||||
|
||||
private transient List<DropParam> 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<IntList>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
11
src/main/java/emu/lunarcore/game/drops/DropMap.java
Normal file
11
src/main/java/emu/lunarcore/game/drops/DropMap.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
99
src/main/java/emu/lunarcore/game/drops/DropParam.java
Normal file
99
src/main/java/emu/lunarcore/game/drops/DropParam.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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> T randomElement(List<T> 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
|
||||
|
||||
Reference in New Issue
Block a user