mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-06-19 14:05:01 +08:00
* add missing EntityTypes * small command refactorings and improvements * move common command patterns and methods to CommandHelpers * let the spawn command detect the entityType instead of spawning every entity as EntityVehicle * add extra options for spawning gadgets for better debuging and testing * More spawn command additions and cleanups+EntityVehicle changes * Moved remaining patterns from GiveCommand and ClearCommand to CommandHelpers * Added patterns for hp, maxhp, atk, def and (monster)ai for the spawn command * Moved intParam parsing via regex to the CommandHelpers * Read most of EntityVehicle stats from the ConfigGadget instead of hardcoding them Co-authored-by: hartie95 <mail@hartie95.de>
207 lines
8.5 KiB
Java
207 lines
8.5 KiB
Java
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 = {
|
|
"<itemId> [x<amount>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
|
"<gadgetId> [x<amount>] [state<state>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>",
|
|
"<monsterId> [x<amount>] [lv<level>] [ai<aiId>] [maxhp<maxhp>] [hp<hp>(0 for infinite)] [atk<atk>] [def<def>] [blk<blockId>] [grp<groupId>] [cfg<configId>] <x> <y> <z>"},
|
|
permission = "server.spawn",
|
|
permissionTargeted = "server.spawn.others")
|
|
public final class SpawnCommand implements CommandHandler {
|
|
private static final Map<Pattern, BiConsumer<SpawnParameters, Integer>> 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<String> 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;
|
|
}
|
|
}
|