mirror of
https://github.com/Melledy/LunarCore.git
synced 2025-12-12 13:24:36 +01:00
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`
This commit is contained in:
@@ -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] <battle monster ids...>. 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.
|
||||
|
||||
@@ -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,8 +23,11 @@ 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] <battle monster ids...>. Spawns a monster or prop near the targeted player.")
|
||||
public class SpawnCommand implements CommandHandler {
|
||||
private static final Set<String> SEPARATORS = Set.of("/", "|", "\\");
|
||||
private int baseNpcMonsterId;
|
||||
private int baseStageId;
|
||||
|
||||
@Override
|
||||
public void execute(CommandArgs args) {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<GameItem> 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<BattleTargetList> 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<StageExcel> stages) {
|
||||
public Battle(Player player, PlayerLineup lineup, List<? extends BattleStage> 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
16
src/main/java/emu/lunarcore/game/battle/BattleStage.java
Normal file
16
src/main/java/emu/lunarcore/game/battle/BattleStage.java
Normal file
@@ -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<IntList> getMonsterWaves();
|
||||
|
||||
}
|
||||
@@ -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<IntList> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,13 +60,21 @@ 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) {
|
||||
this.buffs = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
@@ -114,6 +114,17 @@ public class Handbook {
|
||||
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());
|
||||
writer.println("# Mazes");
|
||||
|
||||
Reference in New Issue
Block a user