mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-23 04:57:18 +00:00
Merge branch 'development' into unstable
# Conflicts: # src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java # src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java # src/main/java/emu/grasscutter/server/packet/recv/HandlerDungeonEntryInfoReq.java
This commit is contained in:
commit
c11b8a53a3
@ -1 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
# spikehd was here :)
|
||||
|
@ -1,122 +1,122 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.GameConstants;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.ScenePointEntry;
|
||||
import emu.grasscutter.data.excels.DungeonData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.server.game.BaseGameSystem;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import java.util.List;
|
||||
|
||||
public class DungeonSystem extends BaseGameSystem {
|
||||
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
|
||||
new BasicDungeonSettleListener();
|
||||
|
||||
public DungeonSystem(GameServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
public void getEntryInfo(Player player, int pointId) {
|
||||
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
|
||||
|
||||
if (entry == null) {
|
||||
// Error
|
||||
player.sendPacket(new PacketDungeonEntryInfoRsp());
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
|
||||
}
|
||||
|
||||
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
|
||||
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
Grasscutter.getLogger()
|
||||
.info(
|
||||
"{}({}) is trying to enter dungeon {}",
|
||||
player.getNickname(),
|
||||
player.getUid(),
|
||||
dungeonId);
|
||||
|
||||
int sceneId = data.getSceneId();
|
||||
player.getScene().setPrevScene(sceneId);
|
||||
|
||||
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
||||
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
|
||||
}
|
||||
|
||||
player.getScene().setPrevScenePoint(pointId);
|
||||
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** used in tower dungeons handoff */
|
||||
public boolean handoffDungeon(
|
||||
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
|
||||
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
Grasscutter.getLogger()
|
||||
.info(
|
||||
"{}({}) is trying to enter tower dungeon {}",
|
||||
player.getNickname(),
|
||||
player.getUid(),
|
||||
dungeonId);
|
||||
|
||||
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
|
||||
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void exitDungeon(Player player) {
|
||||
Scene scene = player.getScene();
|
||||
|
||||
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get previous scene
|
||||
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
|
||||
|
||||
// Get previous position
|
||||
DungeonData dungeonData = scene.getDungeonData();
|
||||
Position prevPos = new Position(GameConstants.START_POSITION);
|
||||
|
||||
if (dungeonData != null) {
|
||||
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
|
||||
|
||||
if (entry != null) {
|
||||
prevPos.set(entry.getPointData().getTranPos());
|
||||
}
|
||||
}
|
||||
// clean temp team if it has
|
||||
player.getTeamManager().cleanTemporaryTeam();
|
||||
player.getTowerManager().clearEntry();
|
||||
|
||||
// Transfer player back to world
|
||||
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
|
||||
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
|
||||
}
|
||||
|
||||
public void updateDailyDungeons() {
|
||||
GameData.getScenePointEntries()
|
||||
.forEach((id, entry) -> entry.getPointData().updateDailyDungeon());
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.GameConstants;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.ScenePointEntry;
|
||||
import emu.grasscutter.data.excels.DungeonData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.server.game.BaseGameSystem;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import java.util.List;
|
||||
|
||||
public class DungeonSystem extends BaseGameSystem {
|
||||
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
|
||||
new BasicDungeonSettleListener();
|
||||
|
||||
public DungeonSystem(GameServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
public void getEntryInfo(Player player, int pointId, int sceneId) {
|
||||
ScenePointEntry entry = GameData.getScenePointEntryById(sceneId, pointId);
|
||||
|
||||
if (entry == null) {
|
||||
// Error
|
||||
player.sendPacket(new PacketDungeonEntryInfoRsp());
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
|
||||
}
|
||||
|
||||
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
|
||||
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
Grasscutter.getLogger()
|
||||
.info(
|
||||
"{}({}) is trying to enter dungeon {}",
|
||||
player.getNickname(),
|
||||
player.getUid(),
|
||||
dungeonId);
|
||||
|
||||
int sceneId = data.getSceneId();
|
||||
player.getScene().setPrevScene(sceneId);
|
||||
|
||||
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
||||
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
|
||||
}
|
||||
|
||||
player.getScene().setPrevScenePoint(pointId);
|
||||
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** used in tower dungeons handoff */
|
||||
public boolean handoffDungeon(
|
||||
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
|
||||
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
Grasscutter.getLogger()
|
||||
.info(
|
||||
"{}({}) is trying to enter tower dungeon {}",
|
||||
player.getNickname(),
|
||||
player.getUid(),
|
||||
dungeonId);
|
||||
|
||||
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
|
||||
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void exitDungeon(Player player) {
|
||||
Scene scene = player.getScene();
|
||||
|
||||
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get previous scene
|
||||
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
|
||||
|
||||
// Get previous position
|
||||
DungeonData dungeonData = scene.getDungeonData();
|
||||
Position prevPos = new Position(GameConstants.START_POSITION);
|
||||
|
||||
if (dungeonData != null) {
|
||||
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
|
||||
|
||||
if (entry != null) {
|
||||
prevPos.set(entry.getPointData().getTranPos());
|
||||
}
|
||||
}
|
||||
// clean temp team if it has
|
||||
player.getTeamManager().cleanTemporaryTeam();
|
||||
player.getTowerManager().clearEntry();
|
||||
|
||||
// Transfer player back to world
|
||||
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
|
||||
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
|
||||
}
|
||||
|
||||
public void updateDailyDungeons() {
|
||||
GameData.getScenePointEntries()
|
||||
.forEach((id, entry) -> entry.getPointData().updateDailyDungeon());
|
||||
}
|
||||
}
|
||||
|
@ -1,191 +1,191 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
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.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
|
||||
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
|
||||
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
|
||||
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
|
||||
import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo;
|
||||
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
|
||||
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass;
|
||||
import emu.grasscutter.server.event.entity.EntityMoveEvent;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
@Opcodes(PacketOpcodes.CombatInvocationsNotify)
|
||||
public class HandlerCombatInvocationsNotify extends PacketHandler {
|
||||
|
||||
private float cachedLandingSpeed = 0;
|
||||
private long cachedLandingTimeMillisecond = 0;
|
||||
private boolean monitorLandingEvent = false;
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload);
|
||||
for (CombatInvokeEntry entry : notif.getInvokeListList()) {
|
||||
// Handle combat invoke
|
||||
switch (entry.getArgumentType()) {
|
||||
case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> {
|
||||
EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData());
|
||||
AttackResult attackResult = hitInfo.getAttackResult();
|
||||
Player player = session.getPlayer();
|
||||
|
||||
// Check if the player is invulnerable.
|
||||
if (attackResult.getAttackerId()
|
||||
!= player.getTeamManager().getCurrentAvatarEntity().getId()
|
||||
&& player.getAbilityManager().isAbilityInvulnerable()) break;
|
||||
|
||||
// Handle damage
|
||||
player.getAttackResults().add(attackResult);
|
||||
player.getEnergyManager().handleAttackHit(hitInfo);
|
||||
}
|
||||
case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> {
|
||||
// Handle movement
|
||||
EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData());
|
||||
GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId());
|
||||
if (entity != null) {
|
||||
// Move player
|
||||
MotionInfo motionInfo = moveInfo.getMotionInfo();
|
||||
MotionState motionState = motionInfo.getState();
|
||||
|
||||
// Call entity move event.
|
||||
EntityMoveEvent event =
|
||||
new EntityMoveEvent(
|
||||
entity,
|
||||
new Position(motionInfo.getPos()),
|
||||
new Position(motionInfo.getRot()),
|
||||
motionState);
|
||||
event.call();
|
||||
|
||||
entity.move(event.getPosition(), event.getRotation());
|
||||
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
|
||||
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
|
||||
entity.setMotionState(motionState);
|
||||
|
||||
session
|
||||
.getPlayer()
|
||||
.getStaminaManager()
|
||||
.handleCombatInvocationsNotify(session, moveInfo, entity);
|
||||
|
||||
// TODO: handle MOTION_FIGHT landing which has a different damage factor
|
||||
// Also, for plunge attacks, LAND_SPEED is always -30 and is not useful.
|
||||
// May need the height when starting plunge attack.
|
||||
|
||||
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets.
|
||||
// Cache land speed for later use.
|
||||
if (motionState == MotionState.MOTION_STATE_LAND_SPEED) {
|
||||
cachedLandingSpeed = motionInfo.getSpeed().getY();
|
||||
cachedLandingTimeMillisecond = System.currentTimeMillis();
|
||||
monitorLandingEvent = true;
|
||||
}
|
||||
if (monitorLandingEvent) {
|
||||
if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) {
|
||||
monitorLandingEvent = false;
|
||||
handleFallOnGround(session, entity, motionState);
|
||||
}
|
||||
}
|
||||
|
||||
// MOTION_STATE_NOTIFY = Dont send to other players
|
||||
if (motionState == MotionState.MOTION_STATE_NOTIFY) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> {
|
||||
EvtAnimatorParameterInfo paramInfo =
|
||||
EvtAnimatorParameterInfo.parseFrom(entry.getCombatData());
|
||||
if (paramInfo.getIsServerCache()) {
|
||||
paramInfo = paramInfo.toBuilder().setIsServerCache(false).build();
|
||||
entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build();
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) {
|
||||
if (session.getPlayer().inGodmode()) {
|
||||
return;
|
||||
}
|
||||
// People have reported that after plunge attack (client sends a FIGHT instead of
|
||||
// FALL_ON_GROUND) they will die
|
||||
// if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping
|
||||
// again.
|
||||
// A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet.
|
||||
// 200ms seems to be a reasonable delay.
|
||||
int maxDelay = 200;
|
||||
long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond;
|
||||
Grasscutter.getLogger()
|
||||
.trace(
|
||||
"MOTION_FALL_ON_GROUND received after "
|
||||
+ actualDelay
|
||||
+ "/"
|
||||
+ maxDelay
|
||||
+ "ms."
|
||||
+ (actualDelay > maxDelay ? " Discard" : ""));
|
||||
if (actualDelay > maxDelay) {
|
||||
return;
|
||||
}
|
||||
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float damageFactor = 0;
|
||||
if (cachedLandingSpeed < -23.5) {
|
||||
damageFactor = 0.33f;
|
||||
}
|
||||
if (cachedLandingSpeed < -25) {
|
||||
damageFactor = 0.5f;
|
||||
}
|
||||
if (cachedLandingSpeed < -26.5) {
|
||||
damageFactor = 0.66f;
|
||||
}
|
||||
if (cachedLandingSpeed < -28) {
|
||||
damageFactor = 1f;
|
||||
}
|
||||
float damage = maxHP * damageFactor;
|
||||
float newHP = currentHP - damage;
|
||||
if (newHP < 0) {
|
||||
newHP = 0;
|
||||
}
|
||||
if (damageFactor > 0) {
|
||||
Grasscutter.getLogger()
|
||||
.debug(
|
||||
currentHP
|
||||
+ "/"
|
||||
+ maxHP
|
||||
+ "\tLandingSpeed: "
|
||||
+ cachedLandingSpeed
|
||||
+ "\tDamageFactor: "
|
||||
+ damageFactor
|
||||
+ "\tDamage: "
|
||||
+ damage
|
||||
+ "\tNewHP: "
|
||||
+ newHP);
|
||||
} else {
|
||||
Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage");
|
||||
}
|
||||
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
|
||||
entity
|
||||
.getWorld()
|
||||
.broadcastPacket(
|
||||
new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
if (newHP == 0) {
|
||||
session
|
||||
.getPlayer()
|
||||
.getStaminaManager()
|
||||
.killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL);
|
||||
}
|
||||
cachedLandingSpeed = 0;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
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.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
|
||||
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
|
||||
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
|
||||
import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo;
|
||||
import emu.grasscutter.net.proto.EvtAnimatorParameterInfoOuterClass.EvtAnimatorParameterInfo;
|
||||
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass;
|
||||
import emu.grasscutter.server.event.entity.EntityMoveEvent;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
|
||||
@Opcodes(PacketOpcodes.CombatInvocationsNotify)
|
||||
public class HandlerCombatInvocationsNotify extends PacketHandler {
|
||||
|
||||
private float cachedLandingSpeed = 0;
|
||||
private long cachedLandingTimeMillisecond = 0;
|
||||
private boolean monitorLandingEvent = false;
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload);
|
||||
for (CombatInvokeEntry entry : notif.getInvokeListList()) {
|
||||
// Handle combat invoke
|
||||
switch (entry.getArgumentType()) {
|
||||
case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> {
|
||||
EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData());
|
||||
AttackResult attackResult = hitInfo.getAttackResult();
|
||||
Player player = session.getPlayer();
|
||||
|
||||
// Check if the player is invulnerable.
|
||||
if (attackResult.getAttackerId()
|
||||
!= player.getTeamManager().getCurrentAvatarEntity().getId()
|
||||
&& player.getAbilityManager().isAbilityInvulnerable()) break;
|
||||
|
||||
// Handle damage
|
||||
player.getAttackResults().add(attackResult);
|
||||
player.getEnergyManager().handleAttackHit(hitInfo);
|
||||
}
|
||||
case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> {
|
||||
// Handle movement
|
||||
EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData());
|
||||
GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId());
|
||||
if (entity != null) {
|
||||
// Move player
|
||||
MotionInfo motionInfo = moveInfo.getMotionInfo();
|
||||
MotionState motionState = motionInfo.getState();
|
||||
|
||||
// Call entity move event.
|
||||
EntityMoveEvent event =
|
||||
new EntityMoveEvent(
|
||||
entity,
|
||||
new Position(motionInfo.getPos()),
|
||||
new Position(motionInfo.getRot()),
|
||||
motionState);
|
||||
event.call();
|
||||
|
||||
entity.move(event.getPosition(), event.getRotation());
|
||||
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
|
||||
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
|
||||
entity.setMotionState(motionState);
|
||||
|
||||
session
|
||||
.getPlayer()
|
||||
.getStaminaManager()
|
||||
.handleCombatInvocationsNotify(session, moveInfo, entity);
|
||||
|
||||
// TODO: handle MOTION_FIGHT landing which has a different damage factor
|
||||
// Also, for plunge attacks, LAND_SPEED is always -30 and is not useful.
|
||||
// May need the height when starting plunge attack.
|
||||
|
||||
// MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets.
|
||||
// Cache land speed for later use.
|
||||
if (motionState == MotionState.MOTION_STATE_LAND_SPEED) {
|
||||
cachedLandingSpeed = motionInfo.getSpeed().getY();
|
||||
cachedLandingTimeMillisecond = System.currentTimeMillis();
|
||||
monitorLandingEvent = true;
|
||||
}
|
||||
if (monitorLandingEvent) {
|
||||
if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) {
|
||||
monitorLandingEvent = false;
|
||||
handleFallOnGround(session, entity, motionState);
|
||||
}
|
||||
}
|
||||
|
||||
// as long as one of these two packets be forwarded to client, the animation of avatar will be interrupted
|
||||
if (motionState == MotionState.MOTION_STATE_NOTIFY || motionState == MotionState.MOTION_STATE_FIGHT) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> {
|
||||
var paramInfo =
|
||||
EvtAnimatorParameterInfo.parseFrom(entry.getCombatData());
|
||||
if (paramInfo.getIsServerCache()) {
|
||||
paramInfo = paramInfo.toBuilder().setIsServerCache(false).build();
|
||||
entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build();
|
||||
}
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) {
|
||||
if (session.getPlayer().inGodmode()) {
|
||||
return;
|
||||
}
|
||||
// People have reported that after plunge attack (client sends a FIGHT instead of
|
||||
// FALL_ON_GROUND) they will die
|
||||
// if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping
|
||||
// again.
|
||||
// A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet.
|
||||
// 200ms seems to be a reasonable delay.
|
||||
int maxDelay = 200;
|
||||
long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond;
|
||||
Grasscutter.getLogger()
|
||||
.trace(
|
||||
"MOTION_FALL_ON_GROUND received after "
|
||||
+ actualDelay
|
||||
+ "/"
|
||||
+ maxDelay
|
||||
+ "ms."
|
||||
+ (actualDelay > maxDelay ? " Discard" : ""));
|
||||
if (actualDelay > maxDelay) {
|
||||
return;
|
||||
}
|
||||
float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float damageFactor = 0;
|
||||
if (cachedLandingSpeed < -23.5) {
|
||||
damageFactor = 0.33f;
|
||||
}
|
||||
if (cachedLandingSpeed < -25) {
|
||||
damageFactor = 0.5f;
|
||||
}
|
||||
if (cachedLandingSpeed < -26.5) {
|
||||
damageFactor = 0.66f;
|
||||
}
|
||||
if (cachedLandingSpeed < -28) {
|
||||
damageFactor = 1f;
|
||||
}
|
||||
float damage = maxHP * damageFactor;
|
||||
float newHP = currentHP - damage;
|
||||
if (newHP < 0) {
|
||||
newHP = 0;
|
||||
}
|
||||
if (damageFactor > 0) {
|
||||
Grasscutter.getLogger()
|
||||
.debug(
|
||||
currentHP
|
||||
+ "/"
|
||||
+ maxHP
|
||||
+ "\tLandingSpeed: "
|
||||
+ cachedLandingSpeed
|
||||
+ "\tDamageFactor: "
|
||||
+ damageFactor
|
||||
+ "\tDamage: "
|
||||
+ damage
|
||||
+ "\tNewHP: "
|
||||
+ newHP);
|
||||
} else {
|
||||
Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage");
|
||||
}
|
||||
entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP);
|
||||
entity
|
||||
.getWorld()
|
||||
.broadcastPacket(
|
||||
new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
|
||||
if (newHP == 0) {
|
||||
session
|
||||
.getPlayer()
|
||||
.getStaminaManager()
|
||||
.killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_TYPE_FALL);
|
||||
}
|
||||
cachedLandingSpeed = 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.DungeonEntryInfoReqOuterClass.DungeonEntryInfoReq;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
|
||||
@Opcodes(PacketOpcodes.DungeonEntryInfoReq)
|
||||
public class HandlerDungeonEntryInfoReq extends PacketHandler {
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload);
|
||||
|
||||
session.getServer().getDungeonSystem().getEntryInfo(session.getPlayer(), req.getPointId());
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.net.packet.Opcodes;
|
||||
import emu.grasscutter.net.packet.PacketHandler;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.net.proto.DungeonEntryInfoReqOuterClass.DungeonEntryInfoReq;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
|
||||
@Opcodes(PacketOpcodes.DungeonEntryInfoReq)
|
||||
public class HandlerDungeonEntryInfoReq extends PacketHandler {
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
DungeonEntryInfoReq req = DungeonEntryInfoReq.parseFrom(payload);
|
||||
|
||||
session.getServer().getDungeonSystem().getEntryInfo(session.getPlayer(), req.getPointId(), req.getSceneId());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user