package emu.grasscutter.command.commands; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.GadgetData; import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.world.Scene; import emu.grasscutter.utils.Position; import lombok.Setter; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.regex.Pattern; import static emu.grasscutter.command.CommandHelpers.*; import static emu.grasscutter.config.Configuration.GAME_OPTIONS; import static emu.grasscutter.utils.Language.translate; @Command( label = "spawn", aliases = {"drop"}, usage = { " [x] [blk] [grp] [cfg] ", " [x] [state] [maxhp] [hp(0 for infinite)] [atk] [def] [blk] [grp] [cfg] ", " [x] [lv] [ai] [maxhp] [hp(0 for infinite)] [atk] [def] [blk] [grp] [cfg] "}, permission = "server.spawn", permissionTargeted = "server.spawn.others") public final class SpawnCommand implements CommandHandler { private static final Map> intCommandHandlers = Map.ofEntries( Map.entry(lvlRegex, SpawnParameters::setLvl), Map.entry(amountRegex, SpawnParameters::setAmount), Map.entry(stateRegex, SpawnParameters::setState), Map.entry(blockRegex, SpawnParameters::setBlockId), Map.entry(groupRegex, SpawnParameters::setGroupId), Map.entry(configRegex, SpawnParameters::setConfigId), Map.entry(maxHPRegex, SpawnParameters::setMaxHP), Map.entry(hpRegex, SpawnParameters::setHp), Map.entry(defRegex, SpawnParameters::setDef), Map.entry(atkRegex, SpawnParameters::setAtk), Map.entry(aiRegex, SpawnParameters::setAi) ); @Override public void execute(Player sender, Player targetPlayer, List args) { SpawnParameters param = new SpawnParameters(); parseIntParameters(args, param, intCommandHandlers); // At this point, first remaining argument MUST be the id and the rest the pos if (args.size() < 1) { sendUsageMessage(sender); // Reachable if someone does `/give lv90` or similar throw new IllegalArgumentException(); } switch (args.size()) { case 4: try { float x, y, z; x = Float.parseFloat(args.get(1)); y = Float.parseFloat(args.get(2)); z = Float.parseFloat(args.get(3)); param.pos = new Position(x, y, z); } catch (NumberFormatException ignored) { CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); } // Fallthrough case 1: try { param.id = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); } break; default: sendUsageMessage(sender); return; } MonsterData monsterData = GameData.getMonsterDataMap().get(param.id); GadgetData gadgetData = GameData.getGadgetDataMap().get(param.id); ItemData itemData = GameData.getItemDataMap().get(param.id); if (monsterData == null && gadgetData == null && itemData == null) { CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); return; } param.scene = targetPlayer.getScene(); if (param.scene.getEntities().size() + param.amount > GAME_OPTIONS.sceneEntityLimit) { param.amount = Math.max(Math.min(GAME_OPTIONS.sceneEntityLimit - param.scene.getEntities().size(), param.amount), 0); CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.limit_reached", param.amount)); if (param.amount <= 0) { return; } } double maxRadius = Math.sqrt(param.amount * 0.2 / Math.PI); if (param.pos == null) { param.pos = targetPlayer.getPosition(); } for (int i = 0; i < param.amount; i++) { Position pos = GetRandomPositionInCircle(param.pos, maxRadius).addY(3); GameEntity entity = null; if (itemData != null) { entity = createItem(itemData, param, pos); } if (gadgetData != null) { pos.addY(-3); entity = createGadget(gadgetData, param, pos, targetPlayer); } if (monsterData != null) { entity = createMonster(monsterData, param, pos); } applyCommonParameters(entity, param); param.scene.addEntity(entity); } CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.success", param.amount, param.id)); } ; private EntityItem createItem(ItemData itemData, SpawnParameters param, Position pos) { return new EntityItem(param.scene, null, itemData, pos, 1, true); } private EntityMonster createMonster(MonsterData monsterData, SpawnParameters param, Position pos) { var entity = new EntityMonster(param.scene, monsterData, pos, param.lvl); if (param.ai != -1) { entity.setAiId(param.ai); } return entity; } private EntityBaseGadget createGadget(GadgetData gadgetData, SpawnParameters param, Position pos, Player targetPlayer) { EntityBaseGadget entity; if (gadgetData.getType() == EntityType.Vehicle) { entity = new EntityVehicle(param.scene, targetPlayer, param.id, 0, pos, targetPlayer.getRotation()); } else { entity = new EntityGadget(param.scene, param.id, pos, targetPlayer.getRotation()); if (param.state != -1) { ((EntityGadget) entity).setState(param.state); } } return entity; } private void applyCommonParameters(GameEntity entity, SpawnParameters param) { if (param.blockId != -1) { entity.setBlockId(param.blockId); } if (param.groupId != -1) { entity.setGroupId(param.groupId); } if (param.configId != -1) { entity.setConfigId(param.configId); } if (param.maxHP != -1) { entity.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, param.maxHP); entity.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, param.maxHP); } if (param.hp != -1) { entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, param.hp == 0 ? Float.MAX_VALUE : param.hp); } if (param.atk != -1) { entity.setFightProperty(FightProperty.FIGHT_PROP_ATTACK, param.atk); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, param.atk); } if (param.def != -1) { entity.setFightProperty(FightProperty.FIGHT_PROP_DEFENSE, param.def); entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, param.def); } } private Position GetRandomPositionInCircle(Position origin, double radius) { Position target = origin.clone(); double angle = Math.random() * 360; double r = Math.sqrt(Math.random() * radius * radius); target.addX((float) (r * Math.cos(angle))).addZ((float) (r * Math.sin(angle))); return target; } private static class SpawnParameters { @Setter public int id; @Setter public int lvl = 1; @Setter public int amount = 1; @Setter public int blockId = -1; @Setter public int groupId = -1; @Setter public int configId = -1; @Setter public int state = -1; @Setter public int hp = -1; @Setter public int maxHP = -1; @Setter public int atk = -1; @Setter public int def = -1; @Setter public int ai = -1; @Setter public Position pos = null; public Scene scene = null; } }