From d87f04f510155dd4f3a842bccf60049aa6297397 Mon Sep 17 00:00:00 2001 From: Melledy <121644117+Melledy@users.noreply.github.com> Date: Sat, 11 May 2024 04:47:46 -0700 Subject: [PATCH] Implement custom battles using the `/spawn` command Example: `/spawn [npc monster id] [battle monster id 1] [battle monster id 2] [battle monster id 3] lv80` --- README.md | 2 +- .../command/commands/SpawnCommand.java | 95 +++++++++++++++++-- .../lunarcore/data/excel/MonsterExcel.java | 1 + .../emu/lunarcore/data/excel/StageExcel.java | 3 +- .../emu/lunarcore/game/battle/Battle.java | 22 +++-- .../game/battle/BattleMonsterWave.java | 5 +- .../lunarcore/game/battle/BattleStage.java | 16 ++++ .../game/battle/CustomBattleStage.java | 42 ++++++++ .../game/challenge/ChallengeEntityLoader.java | 2 +- .../game/rogue/RogueEntityLoader.java | 2 +- .../game/scene/entity/EntityMonster.java | 15 ++- .../java/emu/lunarcore/util/Handbook.java | 11 +++ 12 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 src/main/java/emu/lunarcore/game/battle/BattleStage.java create mode 100644 src/main/java/emu/lunarcore/game/battle/CustomBattleStage.java diff --git a/README.md b/README.md index b5ec58b..416eab7 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Server commands can be run in the server console or in-game. There is a dummy us /refill. Refill your skill points in open world. /reload. Reloads the server config. /scene [scene id] [floor id]. Teleports the player to the specified scene. -/spawn [monster/prop id] x[amount] s[stage id]. Spawns a monster or prop near the targeted player. +/spawn [npc monster id/prop id] s[stage id] x[amount] lv[level] r[radius] . Spawns a monster or prop near the targeted player. /stop. Stops the server /unstuck @[player id]. Unstucks an offline player if they're in a scene that doesn't load. /worldlevel [world level]. Sets the targeted player's equilibrium level. diff --git a/src/main/java/emu/lunarcore/command/commands/SpawnCommand.java b/src/main/java/emu/lunarcore/command/commands/SpawnCommand.java index c25b845..b8913ff 100644 --- a/src/main/java/emu/lunarcore/command/commands/SpawnCommand.java +++ b/src/main/java/emu/lunarcore/command/commands/SpawnCommand.java @@ -1,5 +1,8 @@ package emu.lunarcore.command.commands; +import java.util.Comparator; +import java.util.Set; + import emu.lunarcore.LunarCore; import emu.lunarcore.command.Command; import emu.lunarcore.command.CommandArgs; @@ -10,6 +13,9 @@ import emu.lunarcore.data.config.MonsterInfo; import emu.lunarcore.data.config.PropInfo; import emu.lunarcore.data.excel.NpcMonsterExcel; import emu.lunarcore.data.excel.PropExcel; +import emu.lunarcore.data.excel.StageExcel; +import emu.lunarcore.game.battle.BattleStage; +import emu.lunarcore.game.battle.CustomBattleStage; import emu.lunarcore.game.enums.PropState; import emu.lunarcore.game.player.Player; import emu.lunarcore.game.scene.entity.EntityMonster; @@ -17,9 +23,12 @@ import emu.lunarcore.game.scene.entity.EntityProp; import emu.lunarcore.util.Position; import emu.lunarcore.util.Utils; -@Command(label = "spawn", permission = "player.spawn", requireTarget = true, desc = "/spawn [monster/prop id] [stage id] x[amount] lv[level] r[radius]. Spawns a monster or prop near the targeted player.") +@Command(label = "spawn", aliases = {"s"}, permission = "player.spawn", requireTarget = true, desc = "/spawn [npc monster id/prop id] s[stage id] x[amount] lv[level] r[radius] . Spawns a monster or prop near the targeted player.") public class SpawnCommand implements CommandHandler { - + private static final Set SEPARATORS = Set.of("/", "|", "\\"); + private int baseNpcMonsterId; + private int baseStageId; + @Override public void execute(CommandArgs args) { Player target = args.getTarget(); @@ -29,9 +38,18 @@ public class SpawnCommand implements CommandHandler { return; } - // Get id - int id = Utils.parseSafeInt(args.get(0)); - int stage = Math.max(Utils.parseSafeInt(args.get(1)), 1); + + // Set spawn id + String spawnId = args.get(0); + int id = 0; + if (spawnId.equalsIgnoreCase("monster")) { + id = this.getBaseNpcMonsterId(); + } else { + id = Utils.parseSafeInt(spawnId); + } + + // Get args + int stageId = args.getStage(); int amount = Math.max(args.getAmount(), 1); int radius = Math.max(args.getRank(), 5) * 1000; @@ -44,6 +62,50 @@ public class SpawnCommand implements CommandHandler { // Spawn monster NpcMonsterExcel monsterExcel = GameData.getNpcMonsterExcelMap().get(id); if (monsterExcel != null) { + // Calculate stage + BattleStage stage = null; + if (stageId > 0) { + // Set user specified stage + stage = GameData.getStageExcelMap().get(stageId); + } else if (args.getList().size() <= 1) { + // Get first stage in the excel table + stage = GameData.getStageExcelMap().get(this.getBaseStageId()); + } else { + // Build custom stage + var customStage = new CustomBattleStage(this.getBaseStageId()); + boolean startNewWave = false; + + // Parse extra monster id args + for (int i = 1; i < args.getList().size(); i++) { + String arg = args.get(i); + + if (SEPARATORS.contains(arg)) { + // Wave separator + startNewWave = true; + } else { + // Add monster to wave + int monster = Utils.parseSafeInt(arg); + + if (GameData.getMonsterExcelMap().containsKey(monster)) { + customStage.addMonster(monster, startNewWave); + } + + // Reset + startNewWave = false; + } + } + + // Set stage + if (customStage.getMonsterWaves().size() > 0) { + stage = customStage; + } + } + + if (stage == null) { + args.sendMessage("Error: No stage or monster waves set"); + return; + } + // Get first monster config from floor info that isnt a boss monster GroupInfo groupInfo = null; MonsterInfo monsterInfo = null; @@ -73,7 +135,7 @@ public class SpawnCommand implements CommandHandler { EntityMonster monster = new EntityMonster(target.getScene(), monsterExcel, groupInfo, monsterInfo); monster.getPos().set(pos); monster.setEventId(monsterInfo.getEventID()); - monster.setCustomStageId(stage); + monster.setCustomStage(stage); if (args.getLevel() > 0) { monster.setCustomLevel(Math.min(args.getLevel(), 100)); @@ -131,4 +193,25 @@ public class SpawnCommand implements CommandHandler { args.sendMessage("Error: Invalid id"); } + private int getBaseNpcMonsterId() { + if (this.baseNpcMonsterId == 0) { + var excel = GameData.getNpcMonsterExcelMap().values().stream().min(Comparator.comparing(NpcMonsterExcel::getId)).orElseGet(null); + if (excel != null) { + this.baseNpcMonsterId = excel.getId(); + } + } + + return this.baseNpcMonsterId; + } + + private int getBaseStageId() { + if (this.baseStageId == 0) { + var excel = GameData.getStageExcelMap().values().stream().min(Comparator.comparing(StageExcel::getId)).orElseGet(null); + if (excel != null) { + this.baseStageId = excel.getId(); + } + } + + return this.baseStageId; + } } diff --git a/src/main/java/emu/lunarcore/data/excel/MonsterExcel.java b/src/main/java/emu/lunarcore/data/excel/MonsterExcel.java index 6420ddf..50724c1 100644 --- a/src/main/java/emu/lunarcore/data/excel/MonsterExcel.java +++ b/src/main/java/emu/lunarcore/data/excel/MonsterExcel.java @@ -8,6 +8,7 @@ import lombok.Getter; @ResourceType(name = {"MonsterConfig.json"}) public class MonsterExcel extends GameResource { private int MonsterID; + private long MonsterName; @Override public int getId() { diff --git a/src/main/java/emu/lunarcore/data/excel/StageExcel.java b/src/main/java/emu/lunarcore/data/excel/StageExcel.java index c52eb1b..7b94592 100644 --- a/src/main/java/emu/lunarcore/data/excel/StageExcel.java +++ b/src/main/java/emu/lunarcore/data/excel/StageExcel.java @@ -5,6 +5,7 @@ import java.util.List; import emu.lunarcore.data.GameResource; import emu.lunarcore.data.ResourceType; +import emu.lunarcore.game.battle.BattleStage; import emu.lunarcore.game.enums.StageType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -13,7 +14,7 @@ import lombok.Getter; @Getter @ResourceType(name = {"StageConfig.json"}) -public class StageExcel extends GameResource { +public class StageExcel extends GameResource implements BattleStage { private int StageID; private long StageName; private StageType StageType; diff --git a/src/main/java/emu/lunarcore/game/battle/Battle.java b/src/main/java/emu/lunarcore/game/battle/Battle.java index 96d5be6..ec1134e 100644 --- a/src/main/java/emu/lunarcore/game/battle/Battle.java +++ b/src/main/java/emu/lunarcore/game/battle/Battle.java @@ -7,7 +7,6 @@ import java.util.function.Consumer; import emu.lunarcore.GameConstants; import emu.lunarcore.data.GameData; -import emu.lunarcore.data.excel.StageExcel; import emu.lunarcore.game.avatar.GameAvatar; import emu.lunarcore.game.inventory.GameItem; import emu.lunarcore.game.player.Player; @@ -39,7 +38,7 @@ public class Battle { private final List drops; private final long timestamp; - private StageExcel stage; // Main battle stage + private BattleStage stage; // Main battle stage private IntList battleEvents; // TODO maybe turn it into a map? private Int2ObjectMap battleTargets; // TODO use custom battle target object as value type in case we need to save battles to the db @@ -67,11 +66,11 @@ public class Battle { this.timestamp = System.currentTimeMillis(); } - public Battle(Player player, PlayerLineup lineup, StageExcel stage) { + public Battle(Player player, PlayerLineup lineup, BattleStage stage) { this(player, lineup, stage, true); } - public Battle(Player player, PlayerLineup lineup, StageExcel stage, boolean loadStage) { + public Battle(Player player, PlayerLineup lineup, BattleStage stage, boolean loadStage) { this(player, lineup); this.stage = stage; @@ -80,11 +79,11 @@ public class Battle { } } - public Battle(Player player, PlayerLineup lineup, List stages) { + public Battle(Player player, PlayerLineup lineup, List stages) { this(player, lineup); this.stage = stages.get(0); - for (StageExcel stage : stages) { + for (var stage : stages) { this.loadStage(stage); } } @@ -105,8 +104,11 @@ public class Battle { } // Get stage - StageExcel stage = GameData.getStageExcelMap().get(npcMonster.getStageId()); - if (stage == null) continue; + BattleStage stage = npcMonster.getCustomStage(); + if (stage == null) { + stage = GameData.getStageExcelMap().get(npcMonster.getStageId()); + if (stage == null) continue; + } // Set main battle stage if we havent already if (this.stage == null) { @@ -118,11 +120,11 @@ public class Battle { } } - private void loadStage(StageExcel stage) { + private void loadStage(BattleStage stage) { this.loadStage(stage, null); } - private void loadStage(StageExcel stage, EntityMonster npcMonster) { + private void loadStage(BattleStage stage, EntityMonster npcMonster) { // Build monster waves for (IntList stageMonsterWave : stage.getMonsterWaves()) { // Create battle wave diff --git a/src/main/java/emu/lunarcore/game/battle/BattleMonsterWave.java b/src/main/java/emu/lunarcore/game/battle/BattleMonsterWave.java index 2bbe348..d4ad2c5 100644 --- a/src/main/java/emu/lunarcore/game/battle/BattleMonsterWave.java +++ b/src/main/java/emu/lunarcore/game/battle/BattleMonsterWave.java @@ -1,6 +1,5 @@ package emu.lunarcore.game.battle; -import emu.lunarcore.data.excel.StageExcel; import emu.lunarcore.proto.SceneMonsterOuterClass.SceneMonster; import emu.lunarcore.proto.SceneMonsterWaveOuterClass.SceneMonsterWave; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -10,13 +9,13 @@ import lombok.Setter; @Getter public class BattleMonsterWave { - private final StageExcel stage; + private final BattleStage stage; private IntList monsters; @Setter private int customLevel; - public BattleMonsterWave(StageExcel stage) { + public BattleMonsterWave(BattleStage stage) { this.stage = stage; this.monsters = new IntArrayList(); } diff --git a/src/main/java/emu/lunarcore/game/battle/BattleStage.java b/src/main/java/emu/lunarcore/game/battle/BattleStage.java new file mode 100644 index 0000000..f033619 --- /dev/null +++ b/src/main/java/emu/lunarcore/game/battle/BattleStage.java @@ -0,0 +1,16 @@ +package emu.lunarcore.game.battle; + +import java.util.List; + +import emu.lunarcore.game.enums.StageType; +import it.unimi.dsi.fastutil.ints.IntList; + +public interface BattleStage { + + public int getId(); + + public StageType getStageType(); + + public List getMonsterWaves(); + +} diff --git a/src/main/java/emu/lunarcore/game/battle/CustomBattleStage.java b/src/main/java/emu/lunarcore/game/battle/CustomBattleStage.java new file mode 100644 index 0000000..d796ee9 --- /dev/null +++ b/src/main/java/emu/lunarcore/game/battle/CustomBattleStage.java @@ -0,0 +1,42 @@ +package emu.lunarcore.game.battle; + +import java.util.ArrayList; +import java.util.List; + +import emu.lunarcore.game.enums.StageType; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; + +@Getter +public class CustomBattleStage implements BattleStage { + private int id; + private List monsterWaves; + + public CustomBattleStage(int id) { + this.id = id; + this.monsterWaves = new ArrayList<>(); + } + + @Override + public StageType getStageType() { + return StageType.Mainline; + } + + public void addMonster(int monsterId, boolean startNewWave) { + if (this.monsterWaves.size() == 0 || startNewWave) { + IntList wave = new IntArrayList(); + wave.add(monsterId); + + this.monsterWaves.add(wave); + } else { + IntList wave = this.monsterWaves.get(this.monsterWaves.size() - 1); + + if (wave.size() < 5) { + wave.add(monsterId); + } else { + this.addMonster(monsterId, true); + } + } + } +} diff --git a/src/main/java/emu/lunarcore/game/challenge/ChallengeEntityLoader.java b/src/main/java/emu/lunarcore/game/challenge/ChallengeEntityLoader.java index ccb23e6..108c5bb 100644 --- a/src/main/java/emu/lunarcore/game/challenge/ChallengeEntityLoader.java +++ b/src/main/java/emu/lunarcore/game/challenge/ChallengeEntityLoader.java @@ -69,7 +69,7 @@ public class ChallengeEntityLoader extends SceneEntityLoader { // Create monster from group monster info EntityMonster monster = new EntityMonster(scene, npcMonsterExcel, group, monsterInfo); monster.setEventId(challengeMonsterInfo.getEventId()); - monster.setCustomStageId(challengeMonsterInfo.getEventId()); + monster.setCustomStage(challengeMonsterInfo.getEventId()); return monster; } diff --git a/src/main/java/emu/lunarcore/game/rogue/RogueEntityLoader.java b/src/main/java/emu/lunarcore/game/rogue/RogueEntityLoader.java index 06f2e87..077c731 100644 --- a/src/main/java/emu/lunarcore/game/rogue/RogueEntityLoader.java +++ b/src/main/java/emu/lunarcore/game/rogue/RogueEntityLoader.java @@ -58,7 +58,7 @@ public class RogueEntityLoader extends SceneEntityLoader { // Actually create the monster now EntityMonster monster = new EntityMonster(scene, npcMonster, group, monsterInfo); monster.setEventId(rogueMonster.getEventID()); - monster.setCustomStageId(rogueMonster.getEventID()); + monster.setCustomStage(rogueMonster.getEventID()); return monster; } diff --git a/src/main/java/emu/lunarcore/game/scene/entity/EntityMonster.java b/src/main/java/emu/lunarcore/game/scene/entity/EntityMonster.java index 653777b..637534e 100644 --- a/src/main/java/emu/lunarcore/game/scene/entity/EntityMonster.java +++ b/src/main/java/emu/lunarcore/game/scene/entity/EntityMonster.java @@ -5,6 +5,7 @@ import emu.lunarcore.data.config.GroupInfo; import emu.lunarcore.data.config.MonsterInfo; import emu.lunarcore.data.excel.NpcMonsterExcel; import emu.lunarcore.game.battle.Battle; +import emu.lunarcore.game.battle.BattleStage; import emu.lunarcore.game.inventory.ItemParamMap; import emu.lunarcore.game.scene.Scene; import emu.lunarcore.game.scene.SceneBuff; @@ -37,7 +38,7 @@ public class EntityMonster implements GameEntity, Tickable { @Setter private SceneBuff tempBuff; private int farmElementId; - @Setter private int customStageId; + private BattleStage customStage; @Setter private int customLevel; public EntityMonster(Scene scene, NpcMonsterExcel excel, GroupInfo group, MonsterInfo monsterInfo) { @@ -59,12 +60,20 @@ public class EntityMonster implements GameEntity, Tickable { } public int getStageId() { - if (this.customStageId == 0) { + if (this.customStage == null) { return (this.getEventId() * 10) + worldLevel; } else { - return this.customStageId; + return this.customStage.getId(); } } + + public void setCustomStage(BattleStage stage) { + this.customStage = stage; + } + + public void setCustomStage(int stageId) { + this.customStage = GameData.getStageExcelMap().get(stageId); + } public synchronized SceneBuff addBuff(int caster, int buffId, int duration) { if (this.buffs == null) { diff --git a/src/main/java/emu/lunarcore/util/Handbook.java b/src/main/java/emu/lunarcore/util/Handbook.java index b8239d2..321a7da 100644 --- a/src/main/java/emu/lunarcore/util/Handbook.java +++ b/src/main/java/emu/lunarcore/util/Handbook.java @@ -113,6 +113,17 @@ public class Handbook { writer.print("[Level " + excel.getLevel() + "] "); writer.println(textMap.getOrDefault(excel.getStageName(), "null")); } + + // Dump monsters + writer.println(System.lineSeparator()); + writer.println("# Battle Monsters"); + list = GameData.getMonsterExcelMap().keySet().intStream().sorted().boxed().toList(); + for (int id : list) { + MonsterExcel excel = GameData.getMonsterExcelMap().get(id); + writer.print(excel.getId()); + writer.print(" : "); + writer.println(textMap.getOrDefault(excel.getMonsterName(), "null")); + } // Dump stages writer.println(System.lineSeparator());