package com.jenjinstudios.world.server.message;
import com.jenjinstudios.core.io.Message;
import com.jenjinstudios.world.Actor;
import com.jenjinstudios.world.Location;
import com.jenjinstudios.world.World;
import com.jenjinstudios.world.math.Angle;
import com.jenjinstudios.world.math.MathUtil;
import com.jenjinstudios.world.math.Vector2D;
import com.jenjinstudios.world.server.WorldClientHandler;
import com.jenjinstudios.world.state.MoveState;
import com.jenjinstudios.world.util.ZoneUtils;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Process a StateChangeRequest.
*
* @author Caleb Brinkman
*/
@SuppressWarnings("WeakerAccess")
public class ExecutableStateChangeRequest extends WorldExecutableMessage
{
private static final Logger LOGGER = Logger.getLogger(ExecutableStateChangeRequest.class.getName());
private Angle angle;
/** The new position, corrected for lag. */
private Vector2D position;
/** The position before correction. */
private Vector2D uncorrectedPosition;
private long timePast;
private long timeOfChange;
/**
* Construct a new ExecutableMessage. Must be implemented by subclasses.
*
* @param handler The handler using this ExecutableMessage.
* @param message The message.
*/
public ExecutableStateChangeRequest(WorldClientHandler handler, Message message) {
super(handler, message);
}
@Override
public void runDelayed() {
Actor player = getClientHandler().getPlayer();
double distance = MathUtil.round(player.getMoveSpeed() * ((double) timePast / 1000d), 2);
position = uncorrectedPosition.getVectorInDirection(distance, angle.getStepAngle());
if (!locationWalkable(player))
{
Angle pAngle = player.getAngle().asIdle();
forcePlayerToAngle(player, pAngle);
} else if (!isCorrectionSafe(player))
{
Angle pAngle = player.getAngle();
forcePlayerToAngle(player, pAngle);
} else
{
player.setAngle(angle);
player.setVector2D(position);
}
}
private void forcePlayerToAngle(Actor player, Angle pAngle) {
Vector2D vector2D = player.getVector2D();
MoveState forcedState = new MoveState(pAngle, vector2D, player.getWorld().getLastUpdateCompleted());
player.setForcedState(forcedState);
}
@Override
public void runImmediate() {
double relativeAngle = (double) getMessage().getArgument("relativeAngle");
double absoluteAngle = (double) getMessage().getArgument("absoluteAngle");
double x = (double) getMessage().getArgument("xCoordinate");
double y = (double) getMessage().getArgument("yCoordinate");
timeOfChange = (long) getMessage().getArgument("timeOfChange");
uncorrectedPosition = new Vector2D(x, y);
angle = new Angle(absoluteAngle, relativeAngle);
timePast = (System.currentTimeMillis() - timeOfChange);
}
private boolean locationWalkable(Actor player) {
World world = player.getWorld();
int zoneID = player.getZoneID();
Location location = ZoneUtils.getLocationForCoordinates(world, zoneID, position);
boolean walkable = false;
if (location != null)
{
String prop = location.getProperties().get("walkable");
walkable = !"false".equals(prop);
}
return walkable;
}
private boolean isCorrectionSafe(Actor player) {
double tolerance = player.getMoveSpeed();
Vector2D proposedPlayerOrigin = getPlayerOrigin(player);
double distance = uncorrectedPosition.getDistanceToVector(proposedPlayerOrigin);
boolean distanceWithinTolerance = distance < tolerance;
if (!distanceWithinTolerance)
{
LOGGER.log(Level.FINEST, "Distance to origin oustide of tolerance: {0},{1}",
new Object[]{distance, tolerance});
}
double clientDistance = uncorrectedPosition.getDistanceToVector(position);
double maxCorrect = player.getMoveSpeed();
boolean withinMaxCorrect = clientDistance < maxCorrect;
if (!withinMaxCorrect)
{
LOGGER.log(Level.FINEST, "Distance to correct oustide of tolerance. " +
"Position: {0}, Corrected: {1}, Step Angle: {2}, Time: {3}, TimePast: {4}",
new Object[]{uncorrectedPosition, position, angle, timeOfChange, timePast});
}
// Tolerance of a single update to account for timing discrepency.
return withinMaxCorrect && distanceWithinTolerance;
}
private Vector2D getPlayerOrigin(Actor player) {
double originDistance = player.getVector2D().getDistanceToVector(uncorrectedPosition);
double playerReverseAngle = angle.reverseStepAngle();
return player.getVector2D().getVectorInDirection(originDistance, playerReverseAngle);
}
}