package speedytools.clientside.network;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.util.BlockPos;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import speedytools.common.blocks.BlockWithMetadata;
import speedytools.common.network.*;
import speedytools.common.utilities.ErrorLog;
import speedytools.common.utilities.QuadOrientation;
import speedytools.common.utilities.ResultWithReason;
/**
* User: The Grey Ghost
* Date: 8/03/14
* Used to send commands to the server, receive status updates from the server
* When issued with a command, keeps trying to contact the server until it receives acknowledgement
* Usage:
* (2) changeClientStatus when the client is interested in whether the server is busy
* (3) informSelectionMade
* performToolAction
* performToolUndo
* These are called when the client needs to send the command to the server. Once issued, their
* progress can be followed by calls to getCurrentActionStatus, getCurrentUndoStatus
* These will progress from WAITING to REJECTED, or to PROCESSING and then to COMPLETED
* (4) The current busy status of the server can be read using getServerStatus and getPercentComplete
* NB tick() must be called at frequent intervals to check for timeouts - at least once per second
*/
public class CloneToolsNetworkClient
{
public CloneToolsNetworkClient(PacketHandlerRegistry packetHandlerRegistry, PacketSender i_packetSender)
{
clientStatus = ClientStatus.IDLE;
serverStatus = ServerStatus.IDLE;
lastActionStatus = ActionStatus.NONE_PENDING;
lastUndoStatus = ActionStatus.NONE_PENDING;
lastRejectionReason = "";
packetSender = i_packetSender;
packetHandlerCloneToolStatus = this.new PacketHandlerCloneToolStatus();
Packet250CloneToolStatus.registerHandler(packetHandlerRegistry, packetHandlerCloneToolStatus, Side.CLIENT);
// packetHandlerRegistry.registerHandlerMethod(Side.CLIENT, Packet250Types.PACKET250_TOOL_STATUS_ID.getPacketTypeID(), packetHandlerCloneToolStatus);
packetHandlerCloneToolAcknowledge = this.new PacketHandlerCloneToolAcknowledge();
Packet250CloneToolAcknowledge.registerHandler(packetHandlerRegistry, packetHandlerCloneToolAcknowledge, Side.CLIENT);
// packetHandlerRegistry.registerHandlerMethod(Side.CLIENT, Packet250Types.PACKET250_TOOL_ACKNOWLEDGE_ID.getPacketTypeID(), packetHandlerCloneToolAcknowledge);
}
/*
public void connectedToServer(EntityClientPlayerMP newPlayer)
{
player = newPlayer;
clientStatus = ClientStatus.IDLE;
serverStatus = ServerStatus.IDLE;
lastActionStatus = ActionStatus.NONE_PENDING;
lastUndoStatus = ActionStatus.NONE_PENDING;
}
public void disconnect()
{
player = null;
}
*/
/**
* Informs the server of the new client status
*/
public void changeClientStatus(ClientStatus newClientStatus)
{
// assert player != null;
clientStatus = newClientStatus;
Packet250CloneToolStatus packet = Packet250CloneToolStatus.clientStatusChange(newClientStatus);
if (packet != null) {
packetSender.sendPacket(packet);
lastServerStatusUpdateTime = System.nanoTime();
}
}
/**
* sends the "Selection Performed" command to the server
* @return true for success
*/
public boolean informSelectionMade()
{
Packet250CloneToolUse packet = Packet250CloneToolUse.prepareForLaterAction();
if (packet != null) {
packetSender.sendPacket(packet);
return true;
}
return false;
}
/**
* sends the "Tool Action Performed" command to the server
* @param toolID
* @param x
* @param y
* @param z
* @param quadOrientation the flipped and rotation status of the placement
* @return true for success, false otherwise
*/
public ResultWithReason performComplexToolAction(int toolID, int x, int y, int z, QuadOrientation quadOrientation, BlockPos initialSelectionOrigin)
{
ResultWithReason result = isReadyToPerformAction();
if (!result.succeeded()) return result;
Packet250CloneToolUse packet = Packet250CloneToolUse.performToolAction(currentActionSequenceNumber, toolID, x, y, z, quadOrientation, initialSelectionOrigin);
lastActionPacket = packet;
if (lastActionPacket != null) {
packetSender.sendPacket(lastActionPacket);
lastActionStatus = ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT;
lastActionSentTime = System.nanoTime();
return ResultWithReason.success();
}
return ResultWithReason.failure("I am confused...");
}
/**
* sends the "Tool Action Performed" command to the server for a fill action (orb, sceptre)
* @param toolID
* @param x
* @param y
* @param z
* @param quadOrientation the flipped and rotation status of the placement
* @return true for success, false otherwise
*/
public ResultWithReason performComplexToolFillAction(int toolID, BlockWithMetadata blockWithMetadata,
int x, int y, int z, QuadOrientation quadOrientation,
BlockPos initialSelectionOrigin)
{
ResultWithReason result = isReadyToPerformAction();
if (!result.succeeded()) return result;
Packet250CloneToolUse packet = Packet250CloneToolUse.performToolFillAction(currentActionSequenceNumber, toolID,
blockWithMetadata, x, y, z,
quadOrientation, initialSelectionOrigin);
lastActionPacket = packet;
if (lastActionPacket != null) {
packetSender.sendPacket(lastActionPacket);
lastActionStatus = ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT;
lastActionSentTime = System.nanoTime();
return ResultWithReason.success();
}
return ResultWithReason.failure("I am confused...");
}
/**
* Check whether an action / undo can be started yet
* @return
*/
private ResultWithReason isReadyToPerformAction() {return isReadyToPerform(true);}
private ResultWithReason isReadyToPerformUndo() {return isReadyToPerform(false);}
private ResultWithReason isReadyToPerform(boolean isAction)
{
switch (serverStatus) {
case IDLE: {
break;
}
case PERFORMING_BACKUP: {
return ResultWithReason.failure("Must wait for world backup to finish!");
}
case PERFORMING_YOUR_ACTION: {
return ResultWithReason.failure("Must wait for your earlier spell to finish!");
}
case UNDOING_YOUR_ACTION: {
return ResultWithReason.failure("Must wait for your earlier spell to undo!");
}
case BUSY_WITH_OTHER_PLAYER: {
return ResultWithReason.failure("Must wait for " + nameOfPlayerBeingServiced + " to finish!");
}
default: assert false : "invalid serverStatus " + serverStatus;
}
if (lastUndoStatus != ActionStatus.NONE_PENDING) {
return ResultWithReason.failure();
}
if (isAction && lastActionStatus != ActionStatus.NONE_PENDING) {
return ResultWithReason.failure();
}
return ResultWithReason.success();
}
/**
* sends the "Tool Undo" command to the server
* undoes the last action (or the action currently in progress)
* @return true for success, false otherwise
*/
public ResultWithReason performComplexToolUndo()
{
Packet250CloneToolUse packet;
if (lastActionStatus == ActionStatus.PROCESSING || lastActionStatus == ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT) {
packet = Packet250CloneToolUse.cancelCurrentAction(currentUndoSequenceNumber, currentActionSequenceNumber);
} else {
ResultWithReason result = isReadyToPerformUndo();
if (!result.succeeded()) return result;
packet = Packet250CloneToolUse.performToolUndo(currentUndoSequenceNumber);
}
lastUndoPacket = packet;
if (lastUndoPacket != null) {
packetSender.sendPacket(lastUndoPacket);
lastUndoStatus = ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT;
lastUndoSentTime = System.nanoTime();
return ResultWithReason.success();
}
return ResultWithReason.failure("I am confused...");
}
public byte getServerPercentComplete() {
return serverPercentComplete;
}
public ServerStatus getServerStatus() {
return serverStatus;
}
public IChatComponent getNameOfPlayerBeingServiced() { return nameOfPlayerBeingServiced;}
/**
* respond to an incoming status packet
* @param player
* @param packet
*/
public void handlePacket(EntityPlayerSP player, Packet250CloneToolStatus packet)
{
serverStatus = packet.getServerStatus();
serverPercentComplete = packet.getCompletionPercentage();
nameOfPlayerBeingServiced = packet.getNameOfPlayerBeingServiced();
lastServerStatusUpdateTime = System.nanoTime();
}
/**
* act on an incoming acknowledgement packet
* reject any packets which don't match the current sequencenumber
* reject any packets we're not waiting for
* @param player
* @param packet
*/
public void handlePacket(EntityPlayerSP player, Packet250CloneToolAcknowledge packet)
{
if (lastActionStatus == ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT || lastActionStatus == ActionStatus.PROCESSING) {
if (packet.getActionSequenceNumber() == currentActionSequenceNumber) {
switch (packet.getActionAcknowledgement()) {
case NOUPDATE: {
break;
}
case REJECTED: {
lastActionStatus = ActionStatus.REJECTED;
lastRejectionReason = packet.getReason();
++currentActionSequenceNumber;
break;
}
case ACCEPTED: {
lastActionStatus = ActionStatus.PROCESSING;
lastActionSentTime = System.nanoTime();
break;
}
case COMPLETED: {
lastActionStatus = ActionStatus.COMPLETED;
++currentActionSequenceNumber;
break;
}
default: {
ErrorLog.defaultLog().info("Illegal action Acknowledgement in Packet250CloneToolAcknowledgement");
return;
}
}
}
}
if (lastUndoStatus == ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT || lastUndoStatus == ActionStatus.PROCESSING) {
if (packet.getUndoSequenceNumber() == currentUndoSequenceNumber) {
switch (packet.getUndoAcknowledgement()) {
case NOUPDATE: {
break;
}
case REJECTED: {
lastUndoStatus = ActionStatus.REJECTED;
lastRejectionReason = packet.getReason();
++currentUndoSequenceNumber;
break;
}
case ACCEPTED: {
lastUndoStatus = ActionStatus.PROCESSING;
lastUndoSentTime = System.nanoTime();
break;
}
case COMPLETED: {
lastUndoStatus = ActionStatus.COMPLETED;
++currentUndoSequenceNumber;
break;
}
default: {
ErrorLog.defaultLog().info("Illegal undo Acknowledgement in Packet250CloneToolAcknowledgement");
return;
}
}
}
}
}
/**
* Called once per tick to handle timeouts (if no response obtained, send packet again)
*/
public void tick()
{
// if (player == null) return;
long timenow = System.nanoTime();
if (lastActionStatus == ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT || lastActionStatus == ActionStatus.PROCESSING) {
if (timenow - lastActionSentTime > RESPONSE_TIMEOUT_MS * 1000 * 1000) {
packetSender.sendPacket(lastActionPacket);
lastActionSentTime = timenow;
}
}
if (lastUndoStatus == ActionStatus.WAITING_FOR_ACKNOWLEDGEMENT || lastUndoStatus == ActionStatus.PROCESSING) {
if (timenow - lastUndoSentTime > RESPONSE_TIMEOUT_MS * 1000 * 1000) {
packetSender.sendPacket(lastUndoPacket);
lastUndoSentTime = timenow;
}
}
if (clientStatus != ClientStatus.IDLE && (timenow - lastServerStatusUpdateTime > RESPONSE_TIMEOUT_MS * 1000 * 1000) ) {
Packet250CloneToolStatus packet = Packet250CloneToolStatus.clientStatusChange(clientStatus);
if (packet != null) {
packetSender.sendPacket(packet);
lastServerStatusUpdateTime = timenow;
}
}
}
/**
* retrieves the status of the action currently being peformed
* If the status is REJECTED or COMPLETED, it will revert to NONE_PENDING after the call
* @return
*/
public ActionStatus getCurrentActionStatus()
{
ActionStatus retval = lastActionStatus;
if (lastActionStatus == ActionStatus.COMPLETED || lastActionStatus == ActionStatus.REJECTED) {
lastActionStatus = ActionStatus.NONE_PENDING;
}
return retval;
}
/**
* retrieves the status of the undo currently being performed
* If the status is REJECTED or COMPLETED, it will revert to NONE_PENDING after the call
* @return
*/
public ActionStatus getCurrentUndoStatus()
{
ActionStatus retval = lastUndoStatus;
if (lastUndoStatus == ActionStatus.COMPLETED || lastUndoStatus == ActionStatus.REJECTED) {
lastUndoStatus = ActionStatus.NONE_PENDING;
}
return retval;
}
/** if an action or an undo has been rejected, this may hold a human-readable message
* from the server explaining why.
* @return empty string if no reason given.
*/
public String getLastRejectionReason() {return lastRejectionReason;}
/** retrieves the status of the action currently being performed, without
* acknowledging a REJECTED or COMPLETED, i.e. unlike getCurrentActionStatus
* it won't revert to NONE_PENDING after the call if the status is REJECTED or COMPLETED
* @return
*/
public ActionStatus peekCurrentActionStatus()
{
return lastActionStatus;
}
/** retrieves the status of the undo currently being performed, without
* acknowledging a REJECTED or COMPLETED, i.e. unlike getCurrentUndoStatus
* it won't revert to NONE_PENDING after the call if the status is REJECTED or COMPLETED
* @return
*/
public ActionStatus peekCurrentUndoStatus()
{
return lastUndoStatus;
}
public class PacketHandlerCloneToolStatus implements Packet250CloneToolStatus.PacketHandlerMethod {
@Override
public boolean handlePacket(Packet250CloneToolStatus toolStatusPacket, MessageContext ctx)
{
if (toolStatusPacket == null || !toolStatusPacket.validForSide(Side.CLIENT)) return false;
CloneToolsNetworkClient.this.handlePacket(Minecraft.getMinecraft().thePlayer, toolStatusPacket);
return true;
}
}
public class PacketHandlerCloneToolAcknowledge implements Packet250CloneToolAcknowledge.PacketHandlerMethod {
@Override
public boolean handlePacket(Packet250CloneToolAcknowledge toolAcknowledgePacket, MessageContext ctx)
{
if (toolAcknowledgePacket == null || !toolAcknowledgePacket.validForSide(Side.CLIENT)) return false;
CloneToolsNetworkClient.this.handlePacket(Minecraft.getMinecraft().thePlayer, toolAcknowledgePacket);
return true;
}
}
private PacketHandlerCloneToolStatus packetHandlerCloneToolStatus;
private PacketHandlerCloneToolAcknowledge packetHandlerCloneToolAcknowledge;
// private EntityClientPlayerMP player;
private PacketSender packetSender;
private ClientStatus clientStatus;
private ServerStatus serverStatus;
private byte serverPercentComplete;
private IChatComponent nameOfPlayerBeingServiced = new ChatComponentText("");
private ActionStatus lastActionStatus;
private ActionStatus lastUndoStatus;
private String lastRejectionReason;
private long lastServerStatusUpdateTime; //time in ns.
private long lastActionSentTime; //time in ns.
private long lastUndoSentTime; //time in ns.
private Packet250CloneToolUse lastActionPacket = null;
private Packet250CloneToolUse lastUndoPacket = null;
static int currentActionSequenceNumber = 0;
static int currentUndoSequenceNumber = 0;
private static final int RESPONSE_TIMEOUT_MS = 2000; // how long to wait for a response before sending another query
public static enum ActionStatus
{
NONE_PENDING, WAITING_FOR_ACKNOWLEDGEMENT, REJECTED, PROCESSING, COMPLETED;
public static final ActionStatus[] allValues = {NONE_PENDING, WAITING_FOR_ACKNOWLEDGEMENT, REJECTED, PROCESSING, COMPLETED};
}
}