mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 21:54:08 +00:00
feat: implement player ability state management with rotation and position locking
This commit is contained in:
1 parent
ab1f0d7f92
commit
0b0dcdc58d
8 files changed
+427
-21
No files matched your search
@@ -86,10 +86,20 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
@Override
|
||||
public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, World world, int tick) {
|
||||
if (isPlayerCaster(caster)) {
|
||||
// Player: slam will launch from current position, no target tracking needed
|
||||
targetX = caster.posX;
|
||||
targetY = caster.posY;
|
||||
targetZ = caster.posZ;
|
||||
// Player: update target along look direction during windup
|
||||
if (targetingMode == TargetingMode.AOE_SELF) {
|
||||
targetX = caster.posX;
|
||||
targetY = caster.posY;
|
||||
targetZ = caster.posZ;
|
||||
} else {
|
||||
float yawRad = (float) Math.toRadians(caster.rotationYaw);
|
||||
double dirX = -Math.sin(yawRad);
|
||||
double dirZ = Math.cos(yawRad);
|
||||
double launchDist = Math.max(4.0, maxRange * 0.5);
|
||||
targetX = caster.posX + dirX * launchDist;
|
||||
targetY = caster.posY;
|
||||
targetZ = caster.posZ + dirZ * launchDist;
|
||||
}
|
||||
} else {
|
||||
// NPC: update target position during windup for telegraph tracking
|
||||
if (targetingMode == TargetingMode.AOE_SELF) {
|
||||
@@ -139,18 +149,22 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
}
|
||||
|
||||
/**
|
||||
* Player slam: Launch straight up. Player controls horizontal movement via WASD.
|
||||
* Player slam: Launch in look direction using same ballistic arc as NPC slam.
|
||||
* AOE damage triggers wherever the player lands.
|
||||
*/
|
||||
private void executePlayerSlam(EntityLivingBase caster) {
|
||||
targetX = caster.posX;
|
||||
targetY = caster.posY;
|
||||
targetZ = caster.posZ;
|
||||
// Calculate target position along player's look direction (horizontal only)
|
||||
float yawRad = (float) Math.toRadians(caster.rotationYaw);
|
||||
double dirX = -Math.sin(yawRad);
|
||||
double dirZ = Math.cos(yawRad);
|
||||
|
||||
double vy = calculateLaunchVelocity(Math.max(1.0, leapHeight));
|
||||
caster.motionY = vy;
|
||||
hasLaunched = true;
|
||||
caster.velocityChanged = true;
|
||||
double launchDist = Math.max(4.0, maxRange * 0.5);
|
||||
|
||||
targetX = caster.posX + dirX * launchDist;
|
||||
targetY = caster.posY;
|
||||
targetZ = caster.posZ + dirZ * launchDist;
|
||||
|
||||
launchTowardTarget(caster);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,14 +311,18 @@ public class AbilitySlam extends Ability implements IAbilitySlam {
|
||||
return;
|
||||
}
|
||||
|
||||
// NPC: face toward target destination while in air
|
||||
// Player: free look — player controls their own camera
|
||||
if (!isPreview() && !isPlayerCaster(caster)) {
|
||||
// Face toward target destination while in air (both NPC and Player)
|
||||
if (!isPreview()) {
|
||||
double dx = targetX - caster.posX;
|
||||
double dz = targetZ - caster.posZ;
|
||||
float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0D / Math.PI);
|
||||
caster.rotationYaw = targetYaw;
|
||||
caster.rotationYawHead = targetYaw;
|
||||
if (dx * dx + dz * dz > 0.25) {
|
||||
float targetYaw = (float) (Math.atan2(-dx, dz) * 180.0D / Math.PI);
|
||||
caster.rotationYaw = targetYaw;
|
||||
caster.rotationYawHead = targetYaw;
|
||||
if (isPlayerCaster(caster)) {
|
||||
caster.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import kamkeel.npcs.network.packets.data.SoundManagementPacket;
|
||||
import kamkeel.npcs.network.packets.data.SwingPlayerArmPacket;
|
||||
import kamkeel.npcs.network.packets.data.UpdateAnimationsPacket;
|
||||
import kamkeel.npcs.network.packets.data.VillagerListPacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket;
|
||||
import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket;
|
||||
@@ -588,6 +589,7 @@ public class PacketHandler {
|
||||
DATA_PACKET.registerPacket(new TelegraphSpawnPacket());
|
||||
DATA_PACKET.registerPacket(new TelegraphRemovePacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilitySyncPacket());
|
||||
DATA_PACKET.registerPacket(new PlayerAbilityStatePacket());
|
||||
}
|
||||
|
||||
public void registerPlayerPackets() {
|
||||
|
||||
@@ -63,6 +63,7 @@ public enum EnumDataPacket {
|
||||
TELEGRAPH_SPAWN,
|
||||
TELEGRAPH_REMOVE,
|
||||
PLAYER_ABILITY_SYNC,
|
||||
PLAYER_ABILITY_STATE,
|
||||
|
||||
// Auction System
|
||||
AUCTION_DATA,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package kamkeel.npcs.network.packets.data.ability;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import kamkeel.npcs.network.AbstractPacket;
|
||||
import kamkeel.npcs.network.PacketChannel;
|
||||
import kamkeel.npcs.network.PacketHandler;
|
||||
import kamkeel.npcs.network.enums.EnumDataPacket;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import noppes.npcs.client.ClientAbilityState;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Lightweight packet syncing ability lock state from server to client.
|
||||
* Sent every tick during ability execution when flags change, and once when ability ends.
|
||||
*
|
||||
* Flags byte layout:
|
||||
* bit 0: movement locked (suppress WASD, zero motion)
|
||||
* bit 1: rotation locked (freeze yaw/pitch)
|
||||
* bit 2: has ability movement (ability controls motion — suppress WASD but don't zero)
|
||||
* bit 3: position locked (freeze position)
|
||||
*/
|
||||
public final class PlayerAbilityStatePacket extends AbstractPacket {
|
||||
public static final String packetName = "Data|PlayerAbilityState";
|
||||
|
||||
public static final byte FLAG_MOVEMENT_LOCKED = 1;
|
||||
public static final byte FLAG_ROTATION_LOCKED = 2;
|
||||
public static final byte FLAG_HAS_ABILITY_MOVEMENT = 4;
|
||||
public static final byte FLAG_POSITION_LOCKED = 8;
|
||||
|
||||
private byte flags;
|
||||
private float lockedYaw;
|
||||
private float lockedPitch;
|
||||
|
||||
public PlayerAbilityStatePacket() {
|
||||
}
|
||||
|
||||
public PlayerAbilityStatePacket(byte flags, float lockedYaw, float lockedPitch) {
|
||||
this.flags = flags;
|
||||
this.lockedYaw = lockedYaw;
|
||||
this.lockedPitch = lockedPitch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum getType() {
|
||||
return EnumDataPacket.PLAYER_ABILITY_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketChannel getChannel() {
|
||||
return PacketHandler.DATA_PACKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(ByteBuf out) throws IOException {
|
||||
out.writeByte(flags);
|
||||
if ((flags & FLAG_ROTATION_LOCKED) != 0) {
|
||||
out.writeFloat(lockedYaw);
|
||||
out.writeFloat(lockedPitch);
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@Override
|
||||
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
|
||||
byte flags = in.readByte();
|
||||
float yaw = 0, pitch = 0;
|
||||
if ((flags & FLAG_ROTATION_LOCKED) != 0) {
|
||||
yaw = in.readFloat();
|
||||
pitch = in.readFloat();
|
||||
}
|
||||
ClientAbilityState.update(flags, yaw, pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the current ability state to the player's client.
|
||||
*/
|
||||
public static void sendToPlayer(EntityPlayerMP player, byte flags, float lockedYaw, float lockedPitch) {
|
||||
PacketHandler.Instance.sendToPlayer(new PlayerAbilityStatePacket(flags, lockedYaw, lockedPitch), player);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import cpw.mods.fml.relauncher.Side;
|
||||
import kamkeel.npcs.addon.DBCAddon;
|
||||
import kamkeel.npcs.controllers.AttributeController;
|
||||
import kamkeel.npcs.controllers.SyncController;
|
||||
import kamkeel.npcs.controllers.data.ability.Ability;
|
||||
import kamkeel.npcs.util.AttributeAttackUtil;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -100,9 +101,14 @@ public class ScriptPlayerEventHandler {
|
||||
playerData.abilityData.tick(player);
|
||||
|
||||
// Lock player movement during ability phases that require it
|
||||
// Skip zeroing if the ability has its own movement (Charge/Dash/Slam)
|
||||
if (playerData.abilityData.isMovementLocked()) {
|
||||
player.motionX = 0;
|
||||
player.motionZ = 0;
|
||||
Ability current = playerData.abilityData.getCurrentAbility();
|
||||
if (current == null || !current.hasAbilityMovement()) {
|
||||
player.motionX = 0;
|
||||
player.motionZ = 0;
|
||||
player.velocityChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (playerData.updateClient) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package noppes.npcs.client;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
|
||||
|
||||
/**
|
||||
* Client-side state holder for player ability lock flags.
|
||||
* Updated by {@link PlayerAbilityStatePacket} from the server.
|
||||
* Read by {@link ClientTickHandler} to suppress WASD input and enforce rotation.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class ClientAbilityState {
|
||||
public static boolean movementLocked = false;
|
||||
public static boolean rotationLocked = false;
|
||||
public static boolean hasAbilityMovement = false;
|
||||
public static boolean positionLocked = false;
|
||||
public static float lockedYaw = 0;
|
||||
public static float lockedPitch = 0;
|
||||
|
||||
/**
|
||||
* Update state from a received packet.
|
||||
*/
|
||||
public static void update(byte flags, float yaw, float pitch) {
|
||||
movementLocked = (flags & PlayerAbilityStatePacket.FLAG_MOVEMENT_LOCKED) != 0;
|
||||
rotationLocked = (flags & PlayerAbilityStatePacket.FLAG_ROTATION_LOCKED) != 0;
|
||||
hasAbilityMovement = (flags & PlayerAbilityStatePacket.FLAG_HAS_ABILITY_MOVEMENT) != 0;
|
||||
positionLocked = (flags & PlayerAbilityStatePacket.FLAG_POSITION_LOCKED) != 0;
|
||||
lockedYaw = yaw;
|
||||
lockedPitch = pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether player WASD movement input should be suppressed.
|
||||
* True when movement/position is locked or ability has its own movement.
|
||||
*/
|
||||
public static boolean shouldSuppressMovementInput() {
|
||||
return movementLocked || hasAbilityMovement || positionLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether player camera rotation should be locked.
|
||||
*/
|
||||
public static boolean shouldLockRotation() {
|
||||
return rotationLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all state. Called on world change / disconnect.
|
||||
*/
|
||||
public static void reset() {
|
||||
movementLocked = false;
|
||||
rotationLocked = false;
|
||||
hasAbilityMovement = false;
|
||||
positionLocked = false;
|
||||
lockedYaw = 0;
|
||||
lockedPitch = 0;
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class ClientTickHandler {
|
||||
if ((this.prevWorld == null || mc.theWorld == null) && this.prevWorld != mc.theWorld) {
|
||||
if (mc.theWorld == null) {
|
||||
ClientCacheHandler.clearCache();
|
||||
ClientAbilityState.reset();
|
||||
}
|
||||
this.prevWorld = mc.theWorld;
|
||||
}
|
||||
@@ -72,6 +73,19 @@ public class ClientTickHandler {
|
||||
lastSpecialKeyDown = specialKeyDown;
|
||||
}
|
||||
|
||||
// Suppress player input during ability-controlled phases
|
||||
if (ClientAbilityState.shouldSuppressMovementInput()) {
|
||||
mc.thePlayer.movementInput.moveForward = 0;
|
||||
mc.thePlayer.movementInput.moveStrafe = 0;
|
||||
mc.thePlayer.movementInput.jump = false;
|
||||
}
|
||||
if (ClientAbilityState.shouldLockRotation()) {
|
||||
mc.thePlayer.rotationYaw = ClientAbilityState.lockedYaw;
|
||||
mc.thePlayer.rotationPitch = ClientAbilityState.lockedPitch;
|
||||
mc.thePlayer.prevRotationYaw = ClientAbilityState.lockedYaw;
|
||||
mc.thePlayer.prevRotationPitch = ClientAbilityState.lockedPitch;
|
||||
}
|
||||
|
||||
if (player.ridingEntity instanceof EntityNPCInterface) {
|
||||
EntityNPCInterface mount = (EntityNPCInterface) player.ridingEntity;
|
||||
if (mount.advanced.role == EnumRoleType.Mount && mount.roleInterface instanceof RoleMount) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import noppes.npcs.api.ability.IPlayerAbilityData;
|
||||
import noppes.npcs.controllers.AnimationController;
|
||||
import noppes.npcs.controllers.data.Animation;
|
||||
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilityStatePacket;
|
||||
import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -77,6 +78,26 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
*/
|
||||
private transient EntityLivingBase currentTarget;
|
||||
|
||||
/**
|
||||
* Rotation lock state - freezes player yaw/pitch during ability phases.
|
||||
*/
|
||||
private transient boolean rotationLocked = false;
|
||||
private transient float lockedYaw = 0;
|
||||
private transient float lockedPitch = 0;
|
||||
|
||||
/**
|
||||
* Position lock state - freezes player position during ability phases.
|
||||
*/
|
||||
private transient boolean positionLocked = false;
|
||||
private transient double lockedPosX = 0;
|
||||
private transient double lockedPosY = 0;
|
||||
private transient double lockedPosZ = 0;
|
||||
|
||||
/**
|
||||
* Last synced state flags byte for change detection (avoids spamming packets).
|
||||
*/
|
||||
private transient byte lastSyncedFlags = 0;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CONSTRUCTOR
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -100,6 +121,20 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
tickCurrentAbility(player);
|
||||
|
||||
// Apply rotation and position locks after ability tick
|
||||
applyRotationControl(player);
|
||||
applyPositionLock(player);
|
||||
|
||||
// Sync lock state to client (only sends when flags change)
|
||||
syncAbilityStateIfNeeded(player);
|
||||
} else {
|
||||
// Safety: release orphaned locks if no ability is executing
|
||||
if (rotationLocked || positionLocked) {
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
syncAbilityStateClear(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,17 +149,47 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
switch (currentAbility.getPhase()) {
|
||||
case WINDUP:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.BURST_DELAY) {
|
||||
// Burst replay: re-enter windup - set up locks
|
||||
if (currentAbility.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
if (currentAbility.isMovementLockedDuringWindup() && !currentAbility.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
spawnTelegraph(currentAbility, player, null);
|
||||
playAbilitySound(player, currentAbility.getWindUpSound());
|
||||
playAbilityAnimation(currentAbility.getWindUpAnimation());
|
||||
}
|
||||
currentAbility.onWindUpTick(player, currentTarget, player.worldObj, currentAbility.getCurrentTick());
|
||||
break;
|
||||
|
||||
case ACTIVE:
|
||||
if (phaseChanged && oldPhase == AbilityPhase.WINDUP) {
|
||||
if (phaseChanged && (oldPhase == AbilityPhase.WINDUP || oldPhase == AbilityPhase.BURST_DELAY)) {
|
||||
// Just entered ACTIVE phase - lock all telegraph positions
|
||||
for (TelegraphInstance telegraph : currentAbility.getTelegraphInstances()) {
|
||||
telegraph.lockPosition();
|
||||
}
|
||||
removeTelegraph(currentAbility, player);
|
||||
|
||||
// Handle rotation control transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isRotationLockedDuringActive()) {
|
||||
if (!rotationLocked) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
} else if (rotationLocked) {
|
||||
releaseRotationControl();
|
||||
}
|
||||
|
||||
// Handle position lock transition from WINDUP to ACTIVE
|
||||
if (currentAbility.isMovementLockedDuringActive() && !currentAbility.hasAbilityMovement()) {
|
||||
if (!positionLocked) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
} else if (positionLocked) {
|
||||
releaseLockedPosition();
|
||||
}
|
||||
|
||||
// Play active sound and animation
|
||||
playAbilitySound(player, currentAbility.getActiveSound());
|
||||
playAbilityAnimation(currentAbility.getActiveAnimation());
|
||||
@@ -143,6 +208,18 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
handleAbilityCompletion(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Release locks during burst delay
|
||||
if (currentAbility.getPhase() == AbilityPhase.BURST_DELAY) {
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
}
|
||||
break;
|
||||
|
||||
case BURST_DELAY:
|
||||
// Free movement and rotation during burst delay
|
||||
if (rotationLocked) releaseRotationControl();
|
||||
if (positionLocked) releaseLockedPosition();
|
||||
break;
|
||||
|
||||
case DAZED:
|
||||
@@ -163,6 +240,10 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
currentAbility.onComplete(player, currentTarget);
|
||||
|
||||
// Release all locks
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
|
||||
// Apply universal cooldown (using base cooldownTicks only, not min/max random)
|
||||
if (!currentAbility.isIgnoreCooldown()) {
|
||||
cooldownEndTime = player.worldObj.getTotalWorldTime() + currentAbility.getCooldownTicks();
|
||||
@@ -174,6 +255,9 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
|
||||
// Send cleared state to client
|
||||
syncAbilityStateClear(player);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
@@ -231,9 +315,21 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
|
||||
if (ability.getPhase() == AbilityPhase.ACTIVE) {
|
||||
// Windup was 0 — skip telegraph/windup and go straight to active
|
||||
if (ability.isRotationLockedDuringActive()) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
if (ability.isMovementLockedDuringActive() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
executeImmediate(ability, player);
|
||||
} else {
|
||||
// Normal windup flow
|
||||
if (ability.isRotationLockedDuringWindup()) {
|
||||
captureLockedRotation(player);
|
||||
}
|
||||
if (ability.isMovementLockedDuringWindup() && !ability.hasAbilityMovement()) {
|
||||
captureLockedPosition(player);
|
||||
}
|
||||
spawnTelegraph(ability, player, null);
|
||||
playAbilitySound(player, ability.getWindUpSound());
|
||||
playAbilityAnimation(ability.getWindUpAnimation());
|
||||
@@ -412,9 +508,12 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
if (currentAbility != null && currentAbility.isExecuting()) {
|
||||
currentAbility.interrupt();
|
||||
stopAbilityAnimation();
|
||||
releaseRotationControl();
|
||||
releaseLockedPosition();
|
||||
currentAbility = null;
|
||||
currentAbilityKey = null;
|
||||
currentTarget = null;
|
||||
syncAbilityStateClear(playerData.player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +592,129 @@ public class PlayerAbilityData implements IPlayerAbilityData {
|
||||
&& currentAbility.isMovementLockedForCurrentPhase();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ROTATION CONTROL
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Capture current rotation values to lock player's look direction.
|
||||
*/
|
||||
private void captureLockedRotation(EntityPlayer player) {
|
||||
lockedYaw = player.rotationYaw;
|
||||
lockedPitch = player.rotationPitch;
|
||||
rotationLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release rotation lock.
|
||||
*/
|
||||
private void releaseRotationControl() {
|
||||
rotationLocked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply rotation control on the server side.
|
||||
* Enforces locked rotation values on the player entity.
|
||||
*/
|
||||
private void applyRotationControl(EntityPlayer player) {
|
||||
if (!rotationLocked || currentAbility == null) return;
|
||||
|
||||
if (currentAbility.isRotationLockedForCurrentPhase()) {
|
||||
player.rotationYaw = lockedYaw;
|
||||
player.rotationPitch = lockedPitch;
|
||||
player.prevRotationYaw = lockedYaw;
|
||||
player.prevRotationPitch = lockedPitch;
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
player.rotationYawHead = lockedYaw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// POSITION LOCKING
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Capture current position to lock player in place.
|
||||
*/
|
||||
private void captureLockedPosition(EntityPlayer player) {
|
||||
lockedPosX = player.posX;
|
||||
lockedPosY = player.posY;
|
||||
lockedPosZ = player.posZ;
|
||||
positionLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release position lock.
|
||||
*/
|
||||
private void releaseLockedPosition() {
|
||||
positionLocked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply position lock on the server side.
|
||||
* Snaps player back to locked position and zeroes motion.
|
||||
*/
|
||||
private void applyPositionLock(EntityPlayer player) {
|
||||
if (!positionLocked) return;
|
||||
|
||||
player.setPosition(lockedPosX, lockedPosY, lockedPosZ);
|
||||
player.prevPosX = lockedPosX;
|
||||
player.prevPosY = lockedPosY;
|
||||
player.prevPosZ = lockedPosZ;
|
||||
player.motionX = 0;
|
||||
player.motionZ = 0;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ABILITY STATE SYNC
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Build the current ability state flags byte.
|
||||
*/
|
||||
private byte getAbilityStateFlags() {
|
||||
byte flags = 0;
|
||||
if (currentAbility == null || !currentAbility.isExecuting()) return flags;
|
||||
|
||||
if (currentAbility.isMovementLockedForCurrentPhase()) {
|
||||
flags |= PlayerAbilityStatePacket.FLAG_MOVEMENT_LOCKED;
|
||||
}
|
||||
if (rotationLocked && currentAbility.isRotationLockedForCurrentPhase()) {
|
||||
flags |= PlayerAbilityStatePacket.FLAG_ROTATION_LOCKED;
|
||||
}
|
||||
if (currentAbility.hasAbilityMovement() && currentAbility.getPhase() == AbilityPhase.ACTIVE) {
|
||||
flags |= PlayerAbilityStatePacket.FLAG_HAS_ABILITY_MOVEMENT;
|
||||
}
|
||||
if (positionLocked) {
|
||||
flags |= PlayerAbilityStatePacket.FLAG_POSITION_LOCKED;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send ability state to client if flags changed since last sync.
|
||||
*/
|
||||
private void syncAbilityStateIfNeeded(EntityPlayer player) {
|
||||
if (!(player instanceof EntityPlayerMP)) return;
|
||||
byte flags = getAbilityStateFlags();
|
||||
if (flags != lastSyncedFlags) {
|
||||
lastSyncedFlags = flags;
|
||||
PlayerAbilityStatePacket.sendToPlayer((EntityPlayerMP) player, flags, lockedYaw, lockedPitch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send cleared (all-zero) state to client. Called on ability completion/interrupt.
|
||||
*/
|
||||
private void syncAbilityStateClear(EntityPlayer player) {
|
||||
if (!(player instanceof EntityPlayerMP)) return;
|
||||
if (lastSyncedFlags != 0) {
|
||||
lastSyncedFlags = 0;
|
||||
PlayerAbilityStatePacket.sendToPlayer((EntityPlayerMP) player, (byte) 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// TELEGRAPH
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in new issue
Block a user