diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 311f25a89..c3134558a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -30,6 +30,7 @@ import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.event.player.PlayerMoveEvent; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; @@ -41,22 +42,22 @@ import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; public class EntityAvatar extends GameEntity { private final Avatar avatar; - + private PlayerDieType killedType; private int killedBy; - + public EntityAvatar(Scene scene, Avatar avatar) { super(scene); this.avatar = avatar; this.avatar.setCurrentEnergy(); this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR); - + GameItem weapon = this.getAvatar().getWeapon(); if (weapon != null) { weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON)); } } - + public EntityAvatar(Avatar avatar) { super(null); this.avatar = avatar; @@ -71,7 +72,7 @@ public class EntityAvatar extends GameEntity { public Position getPosition() { return getPlayer().getPos(); } - + @Override public Position getRotation() { return getPlayer().getRotation(); @@ -80,11 +81,11 @@ public class EntityAvatar extends GameEntity { public Avatar getAvatar() { return avatar; } - + public int getKilledBy() { return killedBy; } - + public PlayerDieType getKilledType() { return killedType; } @@ -93,12 +94,12 @@ public class EntityAvatar extends GameEntity { public boolean isAlive() { return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; } - + @Override public Int2FloatOpenHashMap getFightProperties() { return getAvatar().getFightProperties(); } - + public int getWeaponEntityId() { if (getAvatar().getWeapon() != null) { return getAvatar().getWeapon().getWeaponEntityId(); @@ -118,20 +119,20 @@ public class EntityAvatar extends GameEntity { this.killedBy = killerId; clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE); } - + @Override public float heal(float amount) { float healed = super.heal(amount); - + if (healed > 0f) { getScene().broadcastPacket( new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, PropChangeReason.PROP_CHANGE_REASON_ABILITY, ChangeHpReason.CHANGE_HP_REASON_CHANGE_HP_ADD_ABILITY) ); } - + return healed; } - + public void clearEnergy(ChangeEnergyReason reason) { // Fight props. FightProperty curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); @@ -150,7 +151,7 @@ public class EntityAvatar extends GameEntity { this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -maxEnergy, reason)); } } - + public void addEnergy(float amount, PropChangeReason reason) { this.addEnergy(amount, reason, false); } @@ -161,7 +162,7 @@ public class EntityAvatar extends GameEntity { float curEnergy = this.getFightProperty(curEnergyProp); float maxEnergy = this.getFightProperty(maxEnergyProp); - + // Get energy recharge. float energyRecharge = this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); @@ -172,16 +173,16 @@ public class EntityAvatar extends GameEntity { // Determine the new energy value. float newEnergy = Math.min(curEnergy + amount, maxEnergy); - + // Set energy and notify. if (newEnergy != curEnergy) { this.avatar.setCurrentEnergy(curEnergyProp, newEnergy); - + this.getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp)); this.getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason)); } - } - + } + public SceneAvatarInfo getSceneAvatarInfo() { SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder() .setUid(this.getPlayer().getUid()) @@ -198,7 +199,7 @@ public class EntityAvatar extends GameEntity { .setWearingFlycloakId(this.getAvatar().getFlyCloak()) .setCostumeId(this.getAvatar().getCostume()) .setBornTime(this.getAvatar().getBornTime()); - + for (GameItem item : avatar.getEquips().values()) { if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) { avatarInfo.setWeapon(item.createSceneWeaponInfo()); @@ -207,7 +208,7 @@ public class EntityAvatar extends GameEntity { } avatarInfo.addEquipIdList(item.getItemId()); } - + return avatarInfo.build(); } @@ -219,7 +220,7 @@ public class EntityAvatar extends GameEntity { .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder())) .setBornPos(Vector.newBuilder()) .build(); - + SceneEntityInfo.Builder entityInfo = SceneEntityInfo.newBuilder() .setEntityId(getId()) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR) @@ -229,11 +230,11 @@ public class EntityAvatar extends GameEntity { .setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs()) .setLastMoveReliableSeq(this.getLastMoveReliableSeq()) .setLifeState(this.getLifeState().getValue()); - + if (this.getScene() != null) { entityInfo.setMotionInfo(this.getMotionInfo()); } - + for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) { if (entry.getIntKey() == 0) { continue; @@ -241,23 +242,23 @@ public class EntityAvatar extends GameEntity { FightPropPair fightProp = FightPropPair.newBuilder().setPropType(entry.getIntKey()).setPropValue(entry.getFloatValue()).build(); entityInfo.addFightPropList(fightProp); } - + PropPair pair = PropPair.newBuilder() .setType(PlayerProperty.PROP_LEVEL.getId()) .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel())) .build(); entityInfo.addPropList(pair); - + entityInfo.setAvatar(this.getSceneAvatarInfo()); - + return entityInfo.build(); } - + public AbilityControlBlock getAbilityControlBlock() { AvatarData data = this.getAvatar().getAvatarData(); AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder(); int embryoId = 0; - + // Add avatar abilities if (data.getAbilities() != null) { for (int id : data.getAbilities()) { @@ -269,7 +270,7 @@ public class EntityAvatar extends GameEntity { abilityControlBlock.addAbilityEmbryoList(emb); } } - // Add default abilities + // Add default abilities for (int id : GameConstants.DEFAULT_ABILITY_HASHES) { AbilityEmbryo emb = AbilityEmbryo.newBuilder() .setAbilityId(++embryoId) @@ -278,7 +279,7 @@ public class EntityAvatar extends GameEntity { .build(); abilityControlBlock.addAbilityEmbryoList(emb); } - // Add team resonances + // Add team resonances for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) { AbilityEmbryo emb = AbilityEmbryo.newBuilder() .setAbilityId(++embryoId) @@ -310,8 +311,25 @@ public class EntityAvatar extends GameEntity { abilityControlBlock.addAbilityEmbryoList(emb); } } - + // return abilityControlBlock.build(); } + + /** + * Move this entity to a new position. + * Additionally invoke player move event. + * @param newPosition The new position. + * @param rotation The new rotation. + */ + @Override public void move(Position newPosition, Position rotation) { + // Invoke player move event. + PlayerMoveEvent event = new PlayerMoveEvent( + this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, + this.getPosition(), newPosition + ); event.call(); + + // Set position and rotation. + super.move(event.getDestination(), rotation); + } } diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index 21d8f8173..691babe30 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -24,32 +24,32 @@ public abstract class GameEntity { protected int id; private final Scene scene; private SpawnDataEntry spawnEntry; - + private int blockId; private int configId; private int groupId; - + private MotionState moveState; private int lastMoveSceneTimeMs; private int lastMoveReliableSeq; - + // Abilities private Map metaOverrideMap; private Int2ObjectMap metaModifiers; - + public GameEntity(Scene scene) { this.scene = scene; this.moveState = MotionState.MOTION_STATE_NONE; } - + public int getId() { return this.id; } - + public int getEntityType() { return getId() >> 24; } - + public World getWorld() { return this.getScene().getWorld(); } @@ -57,7 +57,7 @@ public abstract class GameEntity { public Scene getScene() { return this.scene; } - + public boolean isAlive() { return true; } @@ -65,14 +65,14 @@ public abstract class GameEntity { public LifeState getLifeState() { return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; } - + public Map getMetaOverrideMap() { if (this.metaOverrideMap == null) { this.metaOverrideMap = new HashMap<>(); } return this.metaOverrideMap; } - + public Int2ObjectMap getMetaModifiers() { if (this.metaModifiers == null) { this.metaModifiers = new Int2ObjectOpenHashMap<>(); @@ -81,11 +81,11 @@ public abstract class GameEntity { } public abstract Int2FloatOpenHashMap getFightProperties(); - + public abstract Position getPosition(); - + public abstract Position getRotation(); - + public MotionState getMotionState() { return moveState; } @@ -109,23 +109,23 @@ public abstract class GameEntity { public void setLastMoveReliableSeq(int lastMoveReliableSeq) { this.lastMoveReliableSeq = lastMoveReliableSeq; } - + public void setFightProperty(FightProperty prop, float value) { this.getFightProperties().put(prop.getId(), value); } - + private void setFightProperty(int id, float value) { this.getFightProperties().put(id, value); } - + public void addFightProperty(FightProperty prop, float value) { this.getFightProperties().put(prop.getId(), getFightProperty(prop) + value); } - + public float getFightProperty(FightProperty prop) { return getFightProperties().getOrDefault(prop.getId(), 0f); } - + public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) { for (Int2FloatMap.Entry entry : getFightProperties().int2FloatEntrySet()) { if (entry.getIntKey() == 0) { @@ -135,7 +135,7 @@ public abstract class GameEntity { entityInfo.addFightPropList(fightProp); } } - + public int getBlockId() { return blockId; } @@ -143,7 +143,7 @@ public abstract class GameEntity { public void setBlockId(int blockId) { this.blockId = blockId; } - + public int getConfigId() { return configId; } @@ -151,7 +151,7 @@ public abstract class GameEntity { public void setConfigId(int configId) { this.configId = configId; } - + public int getGroupId() { return groupId; } @@ -159,7 +159,7 @@ public abstract class GameEntity { public void setGroupId(int groupId) { this.groupId = groupId; } - + protected MotionInfo getMotionInfo() { MotionInfo proto = MotionInfo.newBuilder() .setPos(getPosition().toProto()) @@ -167,7 +167,7 @@ public abstract class GameEntity { .setSpeed(Vector.newBuilder()) .setState(this.getMotionState()) .build(); - + return proto; } @@ -178,7 +178,7 @@ public abstract class GameEntity { public void setSpawnEntry(SpawnDataEntry spawnEntry) { this.spawnEntry = spawnEntry; } - + public float heal(float amount) { if (this.getFightProperties() == null) { return 0f; @@ -186,62 +186,73 @@ public abstract class GameEntity { float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - + if (curHp >= maxHp) { return 0f; } - + float healed = Math.min(maxHp - curHp, amount); this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); - + getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); - + return healed; } - + public void damage(float amount) { damage(amount, 0); } - + public void damage(float amount, int killerId) { // Sanity check if (getFightProperties() == null) { return; } - + // Lose hp addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -amount); - + // Check if dead boolean isDead = false; if (getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); isDead = true; } - + // Packets this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); - + // Check if dead if (isDead) { getScene().killEntity(this, killerId); } } - + + /** + * Move this entity to a new position. + * @param position The new position. + * @param rotation The new rotation. + */ + public void move(Position position, Position rotation) { + // Set the position and rotation. + this.getPosition().set(position); + this.getRotation().set(rotation); + } + /** * Called when this entity is added to the world */ public void onCreate() { - + } - + /** * Called when this entity dies * @param killerId Entity id of the entity that killed this entity */ public void onDeath(int killerId) { - + } - + public abstract SceneEntityInfo toProto(); } diff --git a/src/main/java/emu/grasscutter/server/event/player/PlayerMoveEvent.java b/src/main/java/emu/grasscutter/server/event/player/PlayerMoveEvent.java new file mode 100644 index 000000000..143307a3b --- /dev/null +++ b/src/main/java/emu/grasscutter/server/event/player/PlayerMoveEvent.java @@ -0,0 +1,47 @@ +package emu.grasscutter.server.event.player; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.event.Cancellable; +import emu.grasscutter.server.event.types.PlayerEvent; +import emu.grasscutter.utils.Position; + +/** + * TODO: Allow plugins to change the position of the player. + */ +public final class PlayerMoveEvent extends PlayerEvent implements Cancellable { + private final MoveType type; + private final Position from; + private final Position to; + + public PlayerMoveEvent(Player player, MoveType type, Position from, Position to) { + super(player); + + this.type = type; + this.from = from; + this.to = to; + } + + public MoveType getMoveType() { + return this.type; + } + + public Position getSource() { + return this.from; + } + + public Position getDestination() { + return this.to; + } + + public enum MoveType { + /** + * The player has sent a combat invocation to move. + */ + PLAYER, + + /** + * The server has requested that the player moves. + */ + SERVER + } +} \ No newline at end of file