mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-26 12:08:17 +00:00
Add drowning. Better movement ticking.
This commit is contained in:
parent
81998b9cf9
commit
93b3265d72
@ -104,6 +104,11 @@ public class EntityAvatar extends GameEntity {
|
||||
this.killedType = PlayerDieType.PLAYER_DIE_KILL_BY_MONSTER;
|
||||
this.killedBy = killerId;
|
||||
}
|
||||
|
||||
public void onDeath(PlayerDieType dieType, int killerId) {
|
||||
this.killedType = dieType;
|
||||
this.killedBy = killerId;
|
||||
}
|
||||
|
||||
public SceneAvatarInfo getSceneAvatarInfo() {
|
||||
SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder()
|
||||
|
@ -1,227 +0,0 @@
|
||||
package emu.grasscutter.game.managers.MotionManager;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.LifeState;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerPropNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.lang.Math;
|
||||
|
||||
public class MotionManager {
|
||||
|
||||
private enum Consumption {
|
||||
None(0),
|
||||
|
||||
// consumers
|
||||
CLIMB_START(-500),
|
||||
CLIMBING(-150),
|
||||
CLIMB_JUMP(-2500),
|
||||
DASH(-1800),
|
||||
SPRINT(-360),
|
||||
FLY(-60),
|
||||
SWIM_DASH_START(-200),
|
||||
SWIM_DASH(-200),
|
||||
SWIMMING(-80),
|
||||
|
||||
// restorers
|
||||
STANDBY(500),
|
||||
RUN(500),
|
||||
WALK(500),
|
||||
STANDBY_MOVE(500);
|
||||
|
||||
public final int amount;
|
||||
Consumption(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
private EntityMoveInfoOuterClass.EntityMoveInfo moveInfo;
|
||||
|
||||
private MotionState previousState = MotionState.MOTION_STANDBY;
|
||||
private ArrayList<Position> previousCoordinates = new ArrayList<>();
|
||||
private final Player player;
|
||||
|
||||
private float landSpeed = 0;
|
||||
|
||||
public MotionManager(Player player) {
|
||||
previousCoordinates.add(new Position(0,0,0));
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void handle(GameSession session, GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) {
|
||||
MotionState state = moveInfo.getMotionInfo().getState();
|
||||
setMoveInfo(moveInfo);
|
||||
if (state == MotionState.MOTION_LAND_SPEED) {
|
||||
setLandSpeed(moveInfo.getMotionInfo().getSpeed().getY());
|
||||
}
|
||||
if (state == MotionState.MOTION_FALL_ON_GROUND) {
|
||||
handleFallOnGround(session, entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if(Grasscutter.getConfig().OpenStamina){
|
||||
EntityMoveInfoOuterClass.EntityMoveInfo mInfo = moveInfo;
|
||||
if (mInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotionState state = moveInfo.getMotionInfo().getState();
|
||||
Consumption consumption = Consumption.None;
|
||||
|
||||
boolean isMoving = false;
|
||||
VectorOuterClass.Vector posVector = moveInfo.getMotionInfo().getPos();
|
||||
Position currentCoordinates = new Position(posVector.getX(), posVector.getY(), posVector.getZ());
|
||||
|
||||
float diffX = currentCoordinates.getX() - previousCoordinates.get(0).getX();
|
||||
float diffY = currentCoordinates.getY() - previousCoordinates.get(0).getY();
|
||||
float diffZ = currentCoordinates.getZ() - previousCoordinates.get(0).getZ();
|
||||
|
||||
if (Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.3 || Math.abs(diffZ) > 0.3) {
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
// TODO: refactor these conditions.
|
||||
// CLIMB
|
||||
if (state == MotionState.MOTION_CLIMB) {
|
||||
if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||
consumption = Consumption.CLIMB_START;
|
||||
} else {
|
||||
consumption = Consumption.CLIMBING;
|
||||
}
|
||||
}
|
||||
// JUMP
|
||||
if (state == MotionState.MOTION_CLIMB_JUMP) {
|
||||
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||
consumption = Consumption.CLIMB_JUMP;
|
||||
}
|
||||
}
|
||||
if (state == MotionState.MOTION_JUMP) {
|
||||
if (previousState == MotionState.MOTION_CLIMB) {
|
||||
consumption = Consumption.CLIMB_JUMP;
|
||||
}
|
||||
}
|
||||
// SWIM
|
||||
if (state == MotionState.MOTION_SWIM_MOVE) {
|
||||
consumption = Consumption.SWIMMING;
|
||||
}
|
||||
if (state == MotionState.MOTION_SWIM_DASH) {
|
||||
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
||||
consumption = Consumption.SWIM_DASH_START;
|
||||
} else {
|
||||
consumption = Consumption.SWIM_DASH;
|
||||
}
|
||||
}
|
||||
// DASH
|
||||
if (state == MotionState.MOTION_DASH) {
|
||||
if (previousState == MotionState.MOTION_DASH) {
|
||||
consumption = Consumption.SPRINT;
|
||||
} else {
|
||||
consumption = Consumption.DASH;
|
||||
}
|
||||
}
|
||||
// RUN and WALK
|
||||
if (state == MotionState.MOTION_RUN) {
|
||||
consumption = Consumption.RUN;
|
||||
}
|
||||
if (state == MotionState.MOTION_WALK) {
|
||||
consumption = Consumption.WALK;
|
||||
}
|
||||
// FLY
|
||||
if (state == MotionState.MOTION_FLY) {
|
||||
consumption = Consumption.FLY;
|
||||
}
|
||||
}
|
||||
// STAND
|
||||
if (state == MotionState.MOTION_STANDBY) {
|
||||
consumption = Consumption.STANDBY;
|
||||
}
|
||||
if (state == MotionState.MOTION_STANDBY_MOVE) {
|
||||
consumption = Consumption.STANDBY_MOVE;
|
||||
}
|
||||
|
||||
GameSession session = player.getSession();
|
||||
updateStamina(session, consumption.amount);
|
||||
session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
||||
|
||||
Grasscutter.getLogger().debug(session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + " " + state + " " + isMoving + " " + consumption + " " + consumption.amount);
|
||||
|
||||
previousState = state;
|
||||
previousCoordinates.add(currentCoordinates);
|
||||
if (previousCoordinates.size() > 3) {
|
||||
previousCoordinates.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStamina(GameSession session, int amount) {
|
||||
if (amount == 0) {
|
||||
return;
|
||||
}
|
||||
int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
int newStamina = currentStamina + amount;
|
||||
if (newStamina < 0) {
|
||||
newStamina = 0;
|
||||
}
|
||||
if (newStamina > playerMaxStamina) {
|
||||
newStamina = playerMaxStamina;
|
||||
}
|
||||
session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
||||
}
|
||||
|
||||
public void setMoveInfo(EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) {
|
||||
this.moveInfo = moveInfo;
|
||||
}
|
||||
|
||||
public EntityMoveInfoOuterClass.EntityMoveInfo getMoveInfo() {
|
||||
return moveInfo;
|
||||
}
|
||||
|
||||
public void handleFallOnGround(GameSession session, GameEntity entity) {
|
||||
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float damage = 0;
|
||||
Grasscutter.getLogger().debug("LandSpeed: " + landSpeed);
|
||||
if (landSpeed < -23.5) {
|
||||
damage = (float)(maxHP * 0.33);
|
||||
}
|
||||
if (landSpeed < -25) {
|
||||
damage = (float)(maxHP * 0.5);
|
||||
}
|
||||
if (landSpeed < -26.5) {
|
||||
damage = (float)(maxHP * 0.66);
|
||||
}
|
||||
if (landSpeed < -28) {
|
||||
damage = (maxHP * 1);
|
||||
}
|
||||
float newHP = currentHP - damage;
|
||||
if (newHP < 0) {
|
||||
newHP = 0;
|
||||
}
|
||||
Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP);
|
||||
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
|
||||
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
if (newHP == 0) {
|
||||
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
|
||||
session.getPlayer().getScene().removeEntity(entity);
|
||||
entity.onDeath(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLandSpeed(float landSpeed) {
|
||||
this.landSpeed = landSpeed;
|
||||
}
|
||||
}
|
@ -0,0 +1,359 @@
|
||||
package emu.grasscutter.game.managers.MovementManager;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.LifeState;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass;
|
||||
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.net.proto.VectorOuterClass;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.Math;
|
||||
import java.util.*;
|
||||
|
||||
public class MovementManager {
|
||||
|
||||
public HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>();
|
||||
|
||||
private enum Consumption {
|
||||
None(0),
|
||||
|
||||
// consume
|
||||
CLIMB_START(-500),
|
||||
CLIMBING(-150),
|
||||
CLIMB_JUMP(-2500),
|
||||
DASH(-1800),
|
||||
SPRINT(-360),
|
||||
FLY(-60),
|
||||
SWIM_DASH_START(-200),
|
||||
SWIM_DASH(-200),
|
||||
SWIMMING(-80),
|
||||
|
||||
// restore
|
||||
STANDBY(500),
|
||||
RUN(500),
|
||||
WALK(500),
|
||||
STANDBY_MOVE(500),
|
||||
POWERED_FLY(500);
|
||||
|
||||
public final int amount;
|
||||
Consumption(int amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MotionState previousState = MotionState.MOTION_STANDBY;
|
||||
private MotionState currentState = MotionState.MOTION_STANDBY;
|
||||
private Position previousCoordinates = new Position(0, 0, 0);
|
||||
private Position currentCoordinates = new Position(0, 0, 0);
|
||||
|
||||
private final Player player;
|
||||
|
||||
private float landSpeed = 0;
|
||||
private Timer movementManagerTickTimer;
|
||||
private GameSession cachedSession = null;
|
||||
private GameEntity cachedEntity = null;
|
||||
|
||||
private int staminaRecoverDelay = 0;
|
||||
|
||||
public MovementManager(Player player) {
|
||||
previousCoordinates.add(new Position(0,0,0));
|
||||
this.player = player;
|
||||
|
||||
MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList(
|
||||
MotionState.MOTION_SWIM_MOVE,
|
||||
MotionState.MOTION_SWIM_IDLE,
|
||||
MotionState.MOTION_SWIM_DASH,
|
||||
MotionState.MOTION_SWIM_JUMP
|
||||
)));
|
||||
|
||||
MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList(
|
||||
MotionState.MOTION_STANDBY,
|
||||
MotionState.MOTION_STANDBY_MOVE,
|
||||
MotionState.MOTION_DANGER_STANDBY,
|
||||
MotionState.MOTION_DANGER_STANDBY_MOVE,
|
||||
MotionState.MOTION_LADDER_TO_STANDBY,
|
||||
MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY
|
||||
)));
|
||||
|
||||
MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList(
|
||||
MotionState.MOTION_CLIMB,
|
||||
MotionState.MOTION_CLIMB_JUMP,
|
||||
MotionState.MOTION_STANDBY_TO_CLIMB,
|
||||
MotionState.MOTION_LADDER_IDLE,
|
||||
MotionState.MOTION_LADDER_MOVE,
|
||||
MotionState.MOTION_LADDER_SLIP,
|
||||
MotionState.MOTION_STANDBY_TO_LADDER
|
||||
)));
|
||||
|
||||
MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList(
|
||||
MotionState.MOTION_FLY,
|
||||
MotionState.MOTION_FLY_IDLE,
|
||||
MotionState.MOTION_FLY_SLOW,
|
||||
MotionState.MOTION_FLY_FAST,
|
||||
MotionState.MOTION_POWERED_FLY
|
||||
)));
|
||||
|
||||
MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList(
|
||||
MotionState.MOTION_DASH,
|
||||
MotionState.MOTION_DANGER_DASH,
|
||||
MotionState.MOTION_DASH_BEFORE_SHAKE,
|
||||
MotionState.MOTION_RUN,
|
||||
MotionState.MOTION_DANGER_RUN,
|
||||
MotionState.MOTION_WALK,
|
||||
MotionState.MOTION_DANGER_WALK
|
||||
)));
|
||||
}
|
||||
|
||||
public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) {
|
||||
if (movementManagerTickTimer == null) {
|
||||
movementManagerTickTimer = new Timer();
|
||||
movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200);
|
||||
}
|
||||
// cache info for later use in tick
|
||||
cachedSession = session;
|
||||
cachedEntity = entity;
|
||||
|
||||
MotionInfo motionInfo = moveInfo.getMotionInfo();
|
||||
moveEntity(entity, moveInfo);
|
||||
VectorOuterClass.Vector posVector = motionInfo.getPos();
|
||||
Position newPos = new Position(posVector.getX(),
|
||||
posVector.getY(), posVector.getZ());;
|
||||
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
|
||||
currentCoordinates = newPos;
|
||||
}
|
||||
currentState = motionInfo.getState();
|
||||
Grasscutter.getLogger().debug("" + currentState);
|
||||
handleFallOnGround(motionInfo);
|
||||
}
|
||||
|
||||
public void resetTimer() {
|
||||
movementManagerTickTimer.cancel();
|
||||
movementManagerTickTimer = null;
|
||||
}
|
||||
|
||||
private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) {
|
||||
entity.getPosition().set(moveInfo.getMotionInfo().getPos());
|
||||
entity.getRotation().set(moveInfo.getMotionInfo().getRot());
|
||||
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
|
||||
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
|
||||
entity.setMotionState(moveInfo.getMotionInfo().getState());
|
||||
}
|
||||
|
||||
private boolean isPlayerMoving() {
|
||||
float diffX = currentCoordinates.getX() - previousCoordinates.getX();
|
||||
float diffY = currentCoordinates.getY() - previousCoordinates.getY();
|
||||
float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ();
|
||||
// Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ);
|
||||
return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2;
|
||||
}
|
||||
|
||||
private int getCurrentStamina() {
|
||||
return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
}
|
||||
|
||||
private int getMaximumStamina() {
|
||||
return player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Returns new stamina
|
||||
public int updateStamina(GameSession session, int amount) {
|
||||
int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||
if (amount == 0) {
|
||||
return currentStamina;
|
||||
}
|
||||
int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||
int newStamina = currentStamina + amount;
|
||||
if (newStamina < 0) {
|
||||
newStamina = 0;
|
||||
}
|
||||
if (newStamina > playerMaxStamina) {
|
||||
newStamina = playerMaxStamina;
|
||||
}
|
||||
session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
||||
return newStamina;
|
||||
}
|
||||
|
||||
private void handleFallOnGround(@NotNull MotionInfo motionInfo) {
|
||||
MotionState state = motionInfo.getState();
|
||||
// land speed and fall on ground event arrive in different packets
|
||||
// cache land speed
|
||||
if (state == MotionState.MOTION_LAND_SPEED) {
|
||||
landSpeed = motionInfo.getSpeed().getY();
|
||||
}
|
||||
if (state == MotionState.MOTION_FALL_ON_GROUND) {
|
||||
float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float damage = 0;
|
||||
// Grasscutter.getLogger().debug("LandSpeed: " + landSpeed);
|
||||
if (landSpeed < -23.5) {
|
||||
damage = (float)(maxHP * 0.33);
|
||||
}
|
||||
if (landSpeed < -25) {
|
||||
damage = (float)(maxHP * 0.5);
|
||||
}
|
||||
if (landSpeed < -26.5) {
|
||||
damage = (float)(maxHP * 0.66);
|
||||
}
|
||||
if (landSpeed < -28) {
|
||||
damage = (maxHP * 1);
|
||||
}
|
||||
float newHP = currentHP - damage;
|
||||
if (newHP < 0) {
|
||||
newHP = 0;
|
||||
}
|
||||
// Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP);
|
||||
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
|
||||
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
if (newHP == 0) {
|
||||
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL);
|
||||
}
|
||||
landSpeed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDrowning() {
|
||||
int stamina = getCurrentStamina();
|
||||
if (stamina < 10) {
|
||||
boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState);
|
||||
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming);
|
||||
if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) {
|
||||
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) {
|
||||
cachedSession.send(new PacketAvatarLifeStateChangeNotify(
|
||||
cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(),
|
||||
LifeState.LIFE_DEAD,
|
||||
dieType
|
||||
));
|
||||
cachedSession.send(new PacketLifeStateChangeNotify(
|
||||
cachedEntity,
|
||||
LifeState.LIFE_DEAD,
|
||||
dieType
|
||||
));
|
||||
cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0);
|
||||
cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD));
|
||||
session.getPlayer().getScene().removeEntity(entity);
|
||||
((EntityAvatar)entity).onDeath(dieType, 0);
|
||||
}
|
||||
|
||||
private class MotionManagerTick extends TimerTask
|
||||
{
|
||||
public void run() {
|
||||
if (Grasscutter.getConfig().OpenStamina) {
|
||||
boolean moving = isPlayerMoving();
|
||||
if (moving || (getCurrentStamina() < getMaximumStamina())) {
|
||||
Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina");
|
||||
Consumption consumption = Consumption.None;
|
||||
|
||||
// TODO: refactor these conditions.
|
||||
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
|
||||
if (currentState == MotionState.MOTION_CLIMB) {
|
||||
// CLIMB
|
||||
if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||
consumption = Consumption.CLIMB_START;
|
||||
} else {
|
||||
consumption = Consumption.CLIMBING;
|
||||
}
|
||||
}
|
||||
if (currentState == MotionState.MOTION_CLIMB_JUMP) {
|
||||
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||
consumption = Consumption.CLIMB_JUMP;
|
||||
}
|
||||
}
|
||||
if (currentState == MotionState.MOTION_JUMP) {
|
||||
if (previousState == MotionState.MOTION_CLIMB) {
|
||||
consumption = Consumption.CLIMB_JUMP;
|
||||
}
|
||||
}
|
||||
} else if (MotionStatesCategorized.get("SWIM").contains((currentState))) {
|
||||
// SWIM
|
||||
if (currentState == MotionState.MOTION_SWIM_MOVE) {
|
||||
consumption = Consumption.SWIMMING;
|
||||
}
|
||||
if (currentState == MotionState.MOTION_SWIM_DASH) {
|
||||
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
||||
consumption = Consumption.SWIM_DASH_START;
|
||||
} else {
|
||||
consumption = Consumption.SWIM_DASH;
|
||||
}
|
||||
}
|
||||
} else if (MotionStatesCategorized.get("RUN").contains(currentState)) {
|
||||
// RUN, DASH and WALK
|
||||
// DASH
|
||||
if (currentState == MotionState.MOTION_DASH) {
|
||||
if (previousState == MotionState.MOTION_DASH) {
|
||||
consumption = Consumption.SPRINT;
|
||||
} else {
|
||||
// cost more to start dashing
|
||||
consumption = Consumption.DASH;
|
||||
}
|
||||
}
|
||||
// RUN
|
||||
if (currentState == MotionState.MOTION_RUN) {
|
||||
consumption = Consumption.RUN;
|
||||
}
|
||||
// WALK
|
||||
if (currentState == MotionState.MOTION_WALK) {
|
||||
consumption = Consumption.WALK;
|
||||
}
|
||||
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
|
||||
// FLY
|
||||
consumption = Consumption.FLY;
|
||||
// POWERED_FLY, e.g. wind tunnel
|
||||
if (currentState == MotionState.MOTION_POWERED_FLY) {
|
||||
consumption = Consumption.POWERED_FLY;
|
||||
}
|
||||
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
||||
// STAND
|
||||
if (currentState == MotionState.MOTION_STANDBY) {
|
||||
consumption = Consumption.STANDBY;
|
||||
}
|
||||
if (currentState == MotionState.MOTION_STANDBY_MOVE) {
|
||||
consumption = Consumption.STANDBY_MOVE;
|
||||
}
|
||||
}
|
||||
|
||||
// tick triggered
|
||||
handleDrowning();
|
||||
|
||||
if (cachedSession != null) {
|
||||
if (consumption.amount < 0) {
|
||||
staminaRecoverDelay = 0;
|
||||
}
|
||||
if (consumption.amount > 0) {
|
||||
if (staminaRecoverDelay < 5) {
|
||||
staminaRecoverDelay++;
|
||||
consumption = Consumption.None;
|
||||
}
|
||||
}
|
||||
int newStamina = updateStamina(cachedSession, consumption.amount);
|
||||
cachedSession.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
||||
|
||||
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t" + consumption + "(" + consumption.amount + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousState = currentState;
|
||||
previousCoordinates = new Position(currentCoordinates.getX(),
|
||||
currentCoordinates.getY(), currentCoordinates.getZ());;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.inventory.Inventory;
|
||||
import emu.grasscutter.game.mail.Mail;
|
||||
import emu.grasscutter.game.mail.MailHandler;
|
||||
import emu.grasscutter.game.managers.MotionManager.MotionManager;
|
||||
import emu.grasscutter.game.managers.MovementManager.MovementManager;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.EntityType;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
@ -122,7 +122,7 @@ public class Player {
|
||||
@Transient private final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
|
||||
|
||||
private MapMarksManager mapMarksManager;
|
||||
@Transient private MotionManager motionManager;
|
||||
@Transient private MovementManager movementManager;
|
||||
|
||||
|
||||
@Deprecated
|
||||
@ -164,7 +164,7 @@ public class Player {
|
||||
this.shopLimit = new ArrayList<>();
|
||||
this.messageHandler = null;
|
||||
this.mapMarksManager = new MapMarksManager();
|
||||
this.motionManager = new MotionManager(this);
|
||||
this.movementManager = new MovementManager(this);
|
||||
}
|
||||
|
||||
// On player creation
|
||||
@ -191,7 +191,7 @@ public class Player {
|
||||
this.getRotation().set(0, 307, 0);
|
||||
this.messageHandler = null;
|
||||
this.mapMarksManager = new MapMarksManager();
|
||||
this.motionManager = new MotionManager(this);
|
||||
this.movementManager = new MovementManager(this);
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
@ -974,7 +974,7 @@ public class Player {
|
||||
return mapMarksManager;
|
||||
}
|
||||
|
||||
public MotionManager getMotionManager() { return motionManager; }
|
||||
public MovementManager getMovementManager() { return movementManager; }
|
||||
|
||||
public synchronized void onTick() {
|
||||
// Check ping
|
||||
@ -1004,33 +1004,9 @@ public class Player {
|
||||
this.resetSendPlayerLocTime();
|
||||
}
|
||||
}
|
||||
|
||||
scheduleStaminaNotify();
|
||||
}
|
||||
|
||||
private void scheduleStaminaNotify() {
|
||||
// stamina tick
|
||||
EntityMoveInfoOuterClass.EntityMoveInfo moveInfo = getMotionManager().getMoveInfo();
|
||||
if (moveInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getMotionManager().getMoveInfo().getMotionInfo().getState() == MotionStateOuterClass.MotionState.MOTION_STANDBY) {
|
||||
if (getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) == getProperty(PlayerProperty.PROP_MAX_STAMINA) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i <= 1000; i+=200) {
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
getMotionManager().tick();
|
||||
}
|
||||
}, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void resetSendPlayerLocTime() {
|
||||
|
@ -24,6 +24,7 @@ import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
|
||||
@ -419,27 +420,37 @@ public class TeamManager {
|
||||
if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replacement avatar
|
||||
EntityAvatar replacement = null;
|
||||
int replaceIndex = -1;
|
||||
|
||||
for (int i = 0; i < this.getActiveTeam().size(); i++) {
|
||||
EntityAvatar entity = this.getActiveTeam().get(i);
|
||||
if (entity.isAlive()) {
|
||||
replaceIndex = i;
|
||||
replacement = entity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacement == null) {
|
||||
// No more living team members...
|
||||
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(deadAvatar.getKilledType(), deadAvatar.getKilledBy()));
|
||||
|
||||
PlayerDieType dieType = deadAvatar.getKilledType();
|
||||
int killedBy = deadAvatar.getKilledBy();
|
||||
|
||||
if (dieType == PlayerDieType.PLAYER_DIE_DRAWN) {
|
||||
// Died in water. Do not replace
|
||||
// The official server has skipped this notify and will just respawn the team immediately after the animation.
|
||||
// TODO: Perhaps find a way to get vanilla experience?
|
||||
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
||||
} else {
|
||||
// Set index and spawn replacement member
|
||||
this.setCurrentCharacterIndex(replaceIndex);
|
||||
getPlayer().getScene().addEntity(replacement);
|
||||
// Replacement avatar
|
||||
EntityAvatar replacement = null;
|
||||
int replaceIndex = -1;
|
||||
|
||||
for (int i = 0; i < this.getActiveTeam().size(); i++) {
|
||||
EntityAvatar entity = this.getActiveTeam().get(i);
|
||||
if (entity.isAlive()) {
|
||||
replaceIndex = i;
|
||||
replacement = entity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacement == null) {
|
||||
// No more living team members...
|
||||
getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy));
|
||||
} else {
|
||||
// Set index and spawn replacement member
|
||||
this.setCurrentCharacterIndex(replaceIndex);
|
||||
getPlayer().getScene().addEntity(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
// Response packet
|
||||
@ -492,11 +503,13 @@ public class TeamManager {
|
||||
|
||||
public void respawnTeam() {
|
||||
// Make sure all team members are dead
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
if (entity.isAlive()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Drowning needs revive when there may be other team members still alive.
|
||||
// for (EntityAvatar entity : getActiveTeam()) {
|
||||
// if (entity.isAlive()) {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn
|
||||
|
||||
// Revive all team members
|
||||
for (EntityAvatar entity : getActiveTeam()) {
|
||||
|
@ -1,9 +1,6 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.managers.MotionManager.MotionManager;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.game.props.LifeState;
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
|
||||
@ -11,9 +8,7 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
|
||||
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
|
||||
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.*;
|
||||
|
||||
@Opcodes(PacketOpcodes.CombatInvocationsNotify)
|
||||
public class HandlerCombatInvocationsNotify extends PacketHandler {
|
||||
@ -33,13 +28,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
|
||||
EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData());
|
||||
GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId());
|
||||
if (entity != null) {
|
||||
//move
|
||||
entity.getPosition().set(moveInfo.getMotionInfo().getPos());
|
||||
entity.getRotation().set(moveInfo.getMotionInfo().getRot());
|
||||
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
|
||||
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
|
||||
entity.setMotionState(moveInfo.getMotionInfo().getState());
|
||||
session.getPlayer().getMotionManager().handle(session, entity, moveInfo);
|
||||
session.getPlayer().getMovementManager().handle(session, moveInfo, entity);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -52,7 +41,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler {
|
||||
if (notif.getInvokeListList().size() > 0) {
|
||||
session.getPlayer().getCombatInvokeHandler().update(session.getPlayer());
|
||||
}
|
||||
// Handle attack results last
|
||||
// Handle attack results last
|
||||
while (!session.getPlayer().getAttackResults().isEmpty()) {
|
||||
session.getPlayer().getScene().handleAttack(session.getPlayer().getAttackResults().poll());
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package emu.grasscutter.server.packet.recv;
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.proto.WorldPlayerDieNotifyOuterClass;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketWorldPlayerReviveRsp;
|
||||
|
||||
@Opcodes(PacketOpcodes.WorldPlayerReviveReq)
|
||||
public class HandlerWorldPlayerReviveReq extends PacketHandler {
|
||||
@ -11,6 +13,7 @@ public class HandlerWorldPlayerReviveReq extends PacketHandler {
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
session.getPlayer().getTeamManager().respawnTeam();
|
||||
session.send(new PacketWorldPlayerReviveRsp());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.AvatarLifeStateChangeNotifyOuterClass.AvatarLifeStateChangeNotify;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.net.proto.ServerBuffOuterClass;
|
||||
import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PacketAvatarLifeStateChangeNotify extends BasePacket {
|
||||
|
||||
@ -22,7 +26,7 @@ public class PacketAvatarLifeStateChangeNotify extends BasePacket {
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
public PacketAvatarLifeStateChangeNotify(Avatar avatar,int attackerId,LifeState lifeState) {
|
||||
public PacketAvatarLifeStateChangeNotify(Avatar avatar, int attackerId, LifeState lifeState) {
|
||||
super(PacketOpcodes.AvatarLifeStateChangeNotify);
|
||||
|
||||
AvatarLifeStateChangeNotify proto = AvatarLifeStateChangeNotify.newBuilder()
|
||||
@ -33,4 +37,26 @@ public class PacketAvatarLifeStateChangeNotify extends BasePacket {
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
|
||||
public PacketAvatarLifeStateChangeNotify(Avatar avatar, LifeState lifeState, PlayerDieType dieType) {
|
||||
this(avatar, lifeState, null, "", dieType);
|
||||
}
|
||||
|
||||
public PacketAvatarLifeStateChangeNotify(Avatar avatar, LifeState lifeState, GameEntity sourceEntity,
|
||||
String attackTag, PlayerDieType dieType) {
|
||||
super(PacketOpcodes.AvatarLifeStateChangeNotify);
|
||||
|
||||
AvatarLifeStateChangeNotify.Builder proto = AvatarLifeStateChangeNotify.newBuilder();
|
||||
|
||||
proto.setAvatarGuid(avatar.getGuid());
|
||||
proto.setLifeState(lifeState.getValue());
|
||||
if (sourceEntity != null) {
|
||||
proto.setSourceEntityId(sourceEntity.getId());
|
||||
}
|
||||
proto.setDieType(dieType);
|
||||
proto.setAttackTag((attackTag));
|
||||
|
||||
this.setData(proto.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import emu.grasscutter.game.avatar.Avatar;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.props.LifeState;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.LifeStateChangeNotifyOuterClass.LifeStateChangeNotify;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||
import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PacketLifeStateChangeNotify extends BasePacket {
|
||||
public PacketLifeStateChangeNotify(GameEntity attacker, GameEntity target, LifeState lifeState) {
|
||||
@ -26,7 +31,29 @@ public class PacketLifeStateChangeNotify extends BasePacket {
|
||||
.setLifeState(lifeState.getValue())
|
||||
.setSourceEntityId(attackerId)
|
||||
.build();
|
||||
|
||||
|
||||
this.setData(proto);
|
||||
}
|
||||
|
||||
public PacketLifeStateChangeNotify(GameEntity entity, LifeState lifeState, PlayerDieType dieType) {
|
||||
this(entity, lifeState, null, "", dieType);
|
||||
}
|
||||
|
||||
public PacketLifeStateChangeNotify(GameEntity entity, LifeState lifeState, GameEntity sourceEntity,
|
||||
String attackTag, PlayerDieType dieType) {
|
||||
super(PacketOpcodes.LifeStateChangeNotify);
|
||||
|
||||
LifeStateChangeNotify.Builder proto = LifeStateChangeNotify.newBuilder();
|
||||
|
||||
|
||||
proto.setEntityId(entity.getId());
|
||||
proto.setLifeState(lifeState.getValue());
|
||||
if (sourceEntity != null) {
|
||||
proto.setSourceEntityId(sourceEntity.getId());
|
||||
}
|
||||
proto.setAttackTag(attackTag);
|
||||
proto.setDieType(dieType);
|
||||
|
||||
this.setData(proto.build());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package emu.grasscutter.server.packet.send;
|
||||
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.world.World;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.WorldPlayerReviveRspOuterClass.WorldPlayerReviveRsp;
|
||||
|
||||
public class PacketWorldPlayerReviveRsp extends BasePacket {
|
||||
|
||||
public PacketWorldPlayerReviveRsp() {
|
||||
super(PacketOpcodes.WorldPlayerReviveRsp);
|
||||
|
||||
WorldPlayerReviveRsp.Builder proto = WorldPlayerReviveRsp.newBuilder();
|
||||
|
||||
this.setData(proto.build());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user