Skip to content

Commit

Permalink
Implement working* burst/ultimate invincibility
Browse files Browse the repository at this point in the history
Merge pull request Grasscutters#1622 from Grasscutters/ult-invincibility
  • Loading branch information
KingRainbow44 authored Aug 7, 2022
2 parents 423b235 + 6ec372e commit d56ca20
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 90 deletions.
93 changes: 67 additions & 26 deletions src/main/java/emu/grasscutter/game/ability/AbilityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,84 @@
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import lombok.Getter;

public class AbilityManager extends BasePlayerManager {
public final class AbilityManager extends BasePlayerManager {
HealAbilityManager healAbilityManager;

@Getter private boolean abilityInvulnerable = false;

public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}

public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
healAbilityManager.healHandler(invoke);
this.healAbilityManager.healHandler(invoke);

//Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM:
handleOverrideParam(invoke);
break;
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP:
handleReinitOverrideMap(invoke);
break;
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE:
handleModifierChange(invoke);
break;
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA:
handleMixinCostStamina(invoke);
break;
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL:
handleGenerateElemBall(invoke);
break;
default:
break;
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke);
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke);
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE -> this.handleModifierChange(invoke);
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke);
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke);
default -> {}
}
}

/**
* Invoked when a player starts a skill.
* @param player The player who started the skill.
* @param skillId The skill ID.
* @param casterId The caster ID.
*/
public void onSkillStart(Player player, int skillId, int casterId) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}

// Check if the caster matches the player.
if(player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) {
return;
}

var skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}

// Check if the skill is an elemental burst.
if(skillData.getCostElemVal() <= 0) {
return;
}

// Set the player as invulnerable.
this.abilityInvulnerable = true;
}

/**
* Invoked when a player ends a skill.
* @param player The player who started the skill.
*/
public void onSkillEnd(Player player) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}

// Check if the player is invulnerable.
if(!this.abilityInvulnerable) {
return;
}

// Set the player as not invulnerable.
this.abilityInvulnerable = false;
}

private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());

if (entity == null) {
return;
Expand All @@ -65,7 +106,7 @@ private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
}

private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = player.getScene().getEntityById(invoke.getEntityId());
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());

if (entity == null) {
return;
Expand All @@ -80,7 +121,7 @@ private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception

private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = player.getScene().getEntityById(invoke.getEntityId());
GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
Expand All @@ -104,7 +145,7 @@ private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
return;
}

GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId());
GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
Expand All @@ -117,7 +158,7 @@ private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {

if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
invokeAction(action, target, sourceEntity);
this.invokeAction(action, target, sourceEntity);
}
}

Expand All @@ -133,7 +174,7 @@ private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {

if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
invokeAction(action, target, sourceEntity);
this.invokeAction(action, target, sourceEntity);
}
}

Expand All @@ -145,7 +186,7 @@ private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {

private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}

private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@

@Opcodes(PacketOpcodes.ClientAbilityInitFinishNotify)
public class HandlerClientAbilityInitFinishNotify extends PacketHandler {

@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
ClientAbilityInitFinishNotify notif = ClientAbilityInitFinishNotify.parseFrom(payload);

Player player = session.getPlayer();

// Call skill end in the player's ability manager.
player.getAbilityManager().onSkillEnd(player);

for (AbilityInvokeEntry entry : notif.getInvokesList()) {
player.getAbilityManager().onAbilityInvoke(entry);
player.getClientAbilityInitFinishHandler().addEntry(entry.getForwardType(), entry);
}

if (notif.getInvokesList().size() > 0) {
session.getPlayer().getClientAbilityInitFinishHandler().update(session.getPlayer());
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AttackResultOuterClass;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
Expand All @@ -18,7 +17,6 @@
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass;
import emu.grasscutter.server.event.entity.EntityMoveEvent;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
Expand All @@ -35,23 +33,29 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex
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();
switch (entry.getArgumentType()) {
case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> {
EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData());
AttackResult attackResult = hitInfo.getAttackResult();
Player player = session.getPlayer();

// Handle damage
player.getAttackResults().add(attackResult);
player.getEnergyManager().handleAttackHit(hitInfo);
break;
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();
// 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.
Expand All @@ -60,48 +64,47 @@ 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);
entity.move(event.getPosition(), event.getRotation());
entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime());
entity.setLastMoveReliableSeq(moveInfo.getReliableSeq());
entity.setMotionState(motionState);

session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity);

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.

// 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_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;
}
}
break;
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();
}
break;
default:
break;
}
// 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler {
@Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload);

var player = session.getPlayer();
int skillId = notify.getSkillId();
int casterId = notify.getCasterId();

session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
session.getPlayer().getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
// Call skill perform in the player's ability manager.
player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId);

// Handle skill notify in other managers.
player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId);
}
}
}

0 comments on commit d56ca20

Please sign in to comment.