Alexander Hartmann 08f361954a
Extend spawn command (#1777)
* 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>
2022-09-17 02:34:20 +09:30

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;
}
}