package com.hypixel.hytale.server.core.entity;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.common.util.ListUtil;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.function.function.TriFunction;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector4d;
import com.hypixel.hytale.protocol.BlockPosition;
import com.hypixel.hytale.protocol.ForkedChainId;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.protocol.InteractionChainData;
import com.hypixel.hytale.protocol.InteractionCooldown;
import com.hypixel.hytale.protocol.InteractionState;
import com.hypixel.hytale.protocol.InteractionSyncData;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.protocol.RootInteractionSettings;
import com.hypixel.hytale.protocol.Vector3f;
import com.hypixel.hytale.protocol.WaitForDataFrom;
import com.hypixel.hytale.protocol.packets.interaction.CancelInteractionChain;
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;
import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler;
import com.hypixel.hytale.server.core.modules.interaction.IInteractionSimulationHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.InteractionTypeUtils;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.Collector;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.data.CollectorTag;
import com.hypixel.hytale.server.core.modules.interaction.interaction.operation.Operation;
import com.hypixel.hytale.server.core.modules.time.TimeResource;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.UUIDUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class InteractionManager implements Component<EntityStore> {
public static final double MAX_REACH_DISTANCE = 8.0;
public static final float[] DEFAULT_CHARGE_TIMES = new float[]{0.0F};
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
@Nonnull
private final Int2ObjectMap<InteractionChain> chains = new Int2ObjectOpenHashMap<InteractionChain>();
@Nonnull
private final Int2ObjectMap<InteractionChain> unmodifiableChains;
@Nonnull
private final CooldownHandler cooldownHandler;
@Nonnull
private final LivingEntity entity;
@Nullable
private final PlayerRef playerRef;
private boolean hasRemoteClient;
@Nonnull
private final IInteractionSimulationHandler interactionSimulationHandler;
@Nonnull
private final ObjectList<InteractionSyncData> tempSyncDataList;
private int lastServerChainId;
private int lastClientChainId;
private long packetQueueTime;
private final float[] globalTimeShift;
private final boolean[] globalTimeShiftDirty;
private boolean timeShiftsDirty;
private final ObjectList<SyncInteractionChain> syncPackets;
private long currentTime;
@Nonnull
private final ObjectList<InteractionChain> chainStartQueue;
@Nonnull
private final Predicate<InteractionChain> cachedTickChain;
@Nullable
protected CommandBuffer<EntityStore> commandBuffer;
public InteractionManager(@Nonnull LivingEntity entity, @Nullable PlayerRef playerRef, @Nonnull IInteractionSimulationHandler simulationHandler) {
this.unmodifiableChains = Int2ObjectMaps.<InteractionChain>unmodifiable(this.chains);
this.cooldownHandler = new CooldownHandler();
this.tempSyncDataList = new ObjectArrayList<InteractionSyncData>();
this.globalTimeShift = new float[InteractionType.VALUES.length];
this.globalTimeShiftDirty = new boolean[InteractionType.VALUES.length];
this.syncPackets = new ObjectArrayList<SyncInteractionChain>();
this.currentTime = 1L;
this.chainStartQueue = new ObjectArrayList<InteractionChain>();
this.cachedTickChain = this::tickChain;
this.entity = entity;
this.playerRef = playerRef;
this.hasRemoteClient = playerRef != null;
this.interactionSimulationHandler = simulationHandler;
}
@Nonnull
public Int2ObjectMap<InteractionChain> getChains() {
return this.unmodifiableChains;
}
@Nonnull
public IInteractionSimulationHandler getInteractionSimulationHandler() {
return this.interactionSimulationHandler;
}
private long getOperationTimeoutThreshold() {
if (this.playerRef != null) {
return this.playerRef.getPacketHandler().getOperationTimeoutThreshold();
} else {
assert this.commandBuffer != null;
World world = ((EntityStore)this.commandBuffer.getExternalData()).getWorld();
return (long)(world.getTickStepNanos() / 1000000 * 10);
}
}
private boolean waitingForClient(@Nonnull Ref<EntityStore> ref) {
assert this.commandBuffer != null;
Player playerComponent = (Player)this.commandBuffer.getComponent(ref, Player.getComponentType());
return playerComponent != null ? playerComponent.isWaitingForClientReady() : false;
}
@Deprecated(
forRemoval = true
)
public void setHasRemoteClient(boolean hasRemoteClient) {
this.hasRemoteClient = hasRemoteClient;
}
@Deprecated
public void copyFrom(@Nonnull InteractionManager interactionManager) {
this.chains.putAll(interactionManager.chains);
}
public void tick(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, float dt) {
this.currentTime += (long)((EntityStore)commandBuffer.getExternalData()).getWorld().getTickStepNanos();
this.commandBuffer = commandBuffer;
this.clearAllGlobalTimeShift(dt);
this.cooldownHandler.tick(dt);
for(InteractionChain interactionChain : this.chainStartQueue) {
this.executeChain0(ref, interactionChain);
}
this.chainStartQueue.clear();
Deque<SyncInteractionChain> packetQueue = null;
if (this.playerRef != null) {
packetQueue = ((GamePacketHandler)this.playerRef.getPacketHandler()).getInteractionPacketQueue();
}
if (packetQueue != null && !packetQueue.isEmpty()) {
for(boolean first = true; this.tryConsumePacketQueue(ref, packetQueue) || first; first = false) {
if (!this.chains.isEmpty()) {
this.chains.values().removeIf(this.cachedTickChain);
}
float cooldownDt = 0.0F;
for(float shift : this.globalTimeShift) {
cooldownDt = Math.max(cooldownDt, shift);
}
if (cooldownDt > 0.0F) {
this.cooldownHandler.tick(cooldownDt);
}
}
this.commandBuffer = null;
} else {
if (!this.chains.isEmpty()) {
this.chains.values().removeIf(this.cachedTickChain);
}
this.commandBuffer = null;
}
}
private boolean tryConsumePacketQueue(@Nonnull Ref<EntityStore> ref, @Nonnull Deque<SyncInteractionChain> packetQueue) {
Iterator<SyncInteractionChain> it = packetQueue.iterator();
boolean finished = false;
boolean desynced = false;
int highestChainId = -1;
boolean changed = false;
label99:
while(it.hasNext()) {
SyncInteractionChain packet = (SyncInteractionChain)it.next();
if (packet.desync) {
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Client packet flagged as desync");
}
desynced = true;
}
InteractionChain chain = (InteractionChain)this.chains.get(packet.chainId);
if (chain != null && packet.forkedId != null) {
for(ForkedChainId id = packet.forkedId; id != null; id = id.forkedId) {
InteractionChain subChain = chain.getForkedChain(id);
if (subChain == null) {
InteractionChain.TempChain tempChain = chain.getTempForkedChain(id);
if (tempChain != null) {
tempChain.setBaseForkedChainId(id);
ForkedChainId lastId = id;
for(ForkedChainId queuedTime = id.forkedId; queuedTime != null; queuedTime = queuedTime.forkedId) {
tempChain = tempChain.getOrCreateTempForkedChain(queuedTime);
tempChain.setBaseForkedChainId(queuedTime);
lastId = queuedTime;
}
tempChain.setForkedChainId(packet.forkedId);
tempChain.setBaseForkedChainId(lastId);
tempChain.setChainData(packet.data);
this.sync(ref, tempChain, packet);
changed = true;
it.remove();
this.packetQueueTime = 0L;
}
continue label99;
}
chain = subChain;
}
}
highestChainId = Math.max(highestChainId, packet.chainId);
if (chain == null && !finished) {
if (this.syncStart(ref, packet)) {
changed = true;
it.remove();
this.packetQueueTime = 0L;
} else {
if (!this.waitingForClient(ref)) {
long queuedTime;
if (this.packetQueueTime == 0L) {
this.packetQueueTime = this.currentTime;
queuedTime = 0L;
} else {
queuedTime = this.currentTime - this.packetQueueTime;
}
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Queued chain %d for %s", packet.chainId, FormatUtil.nanosToString(queuedTime));
}
if (queuedTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold())) {
this.sendCancelPacket(packet.chainId, packet.forkedId);
it.remove();
context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Discarding packet due to queuing for too long: %s", packet);
}
}
}
if (!desynced) {
finished = true;
}
}
} else if (chain != null) {
this.sync(ref, chain, packet);
changed = true;
it.remove();
this.packetQueueTime = 0L;
} else if (desynced) {
this.sendCancelPacket(packet.chainId, packet.forkedId);
it.remove();
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
ctx.log("Discarding packet due to desync: %s", packet);
}
}
if (desynced && !packetQueue.isEmpty()) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Discarding previous packets in queue: (before) %d", packetQueue.size());
}
packetQueue.removeIf((v) -> {
boolean shouldRemove = this.getChain(v.chainId, v.forkedId) == null && UUIDUtil.isEmptyOrNull(v.data.proxyId) && v.initial;
if (shouldRemove) {
HytaleLogger.Api ctx1 = LOGGER.at(Level.FINE);
if (ctx1.isEnabled()) {
ctx1.log("Discarding: %s", v);
}
this.sendCancelPacket(v.chainId, v.forkedId);
}
return shouldRemove;
});
ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Discarded previous packets in queue: (after) %d", packetQueue.size());
}
}
return changed;
}
@Nullable
private InteractionChain getChain(int chainId, @Nullable ForkedChainId forkedChainId) {
InteractionChain chain = (InteractionChain)this.chains.get(chainId);
if (chain != null && forkedChainId != null) {
for(ForkedChainId id = forkedChainId; id != null; id = id.forkedId) {
InteractionChain subChain = chain.getForkedChain(id);
if (subChain == null) {
return null;
}
chain = subChain;
}
}
return chain;
}
private boolean tickChain(@Nonnull InteractionChain chain) {
if (chain.wasPreTicked()) {
chain.setPreTicked(false);
return false;
} else {
if (!this.hasRemoteClient) {
chain.updateSimulatedState();
}
chain.getForkedChains().values().removeIf(this.cachedTickChain);
Ref<EntityStore> ref = this.entity.getReference();
assert ref != null;
if (chain.getServerState() != InteractionState.NotFinished) {
if (chain.requiresClient() && chain.getClientState() == InteractionState.NotFinished) {
if (!this.waitingForClient(ref)) {
if (chain.getWaitingForClientFinished() == 0L) {
chain.setWaitingForClientFinished(this.currentTime);
}
long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - chain.getWaitingForClientFinished());
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Server finished chain but client hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillis);
}
long threshold = this.getOperationTimeoutThreshold();
TimeResource timeResource = (TimeResource)this.commandBuffer.getResource(TimeResource.getResourceType());
if (timeResource.getTimeDilationModifier() == 1.0F && waitMillis > threshold) {
this.sendCancelPacket(chain);
return chain.getForkedChains().isEmpty();
}
}
return false;
} else {
LOGGER.at(Level.FINE).log("Remove Chain: %d, %s", chain.getChainId(), chain);
this.handleCancelledChain(ref, chain);
chain.onCompletion(this.cooldownHandler, this.hasRemoteClient);
return chain.getForkedChains().isEmpty();
}
} else {
int baseOpIndex = chain.getOperationIndex();
try {
this.doTickChain(ref, chain);
} catch (ChainCancelledException e) {
chain.setServerState(e.state);
chain.setClientState(e.state);
chain.updateServerState();
if (!this.hasRemoteClient) {
chain.updateSimulatedState();
}
if (chain.requiresClient()) {
this.sendSyncPacket(chain, baseOpIndex, this.tempSyncDataList);
this.sendCancelPacket(chain);
}
}
if (chain.getServerState() != InteractionState.NotFinished) {
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Server finished chain: %d-%s, %s in %fs", chain.getChainId(), chain.getForkedChainId(), chain, chain.getTimeInSeconds());
}
if (!chain.requiresClient() || chain.getClientState() != InteractionState.NotFinished) {
context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Remove Chain: %d-%s, %s", chain.getChainId(), chain.getForkedChainId(), chain);
}
this.handleCancelledChain(ref, chain);
chain.onCompletion(this.cooldownHandler, this.hasRemoteClient);
return chain.getForkedChains().isEmpty();
}
} else if (chain.getClientState() != InteractionState.NotFinished && !this.waitingForClient(ref)) {
if (chain.getWaitingForServerFinished() == 0L) {
chain.setWaitingForServerFinished(this.currentTime);
}
long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - chain.getWaitingForServerFinished());
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Client finished chain but server hasn't! %d, %s, %s", chain.getChainId(), chain, waitMillis);
}
long threshold = this.getOperationTimeoutThreshold();
if (waitMillis > threshold) {
LOGGER.at(Level.SEVERE).log("Client finished chain earlier than server! %d, %s", chain.getChainId(), chain);
}
}
return false;
}
}
}
private void handleCancelledChain(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain) {
assert this.commandBuffer != null;
RootInteraction root = chain.getRootInteraction();
int maxOperations = root.getOperationMax();
if (chain.getOperationCounter() < maxOperations) {
InteractionEntry entry = chain.getInteraction(chain.getOperationIndex());
if (entry != null) {
Operation operation = root.getOperation(chain.getOperationCounter());
if (operation == null) {
throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + root.getId() + "'");
} else {
InteractionContext context = chain.getContext();
entry.getServerState().state = InteractionState.Failed;
if (entry.getClientState() != null) {
entry.getClientState().state = InteractionState.Failed;
}
try {
context.initEntry(chain, entry, this.entity);
TimeResource timeResource = (TimeResource)this.commandBuffer.getResource(TimeResource.getResourceType());
operation.handle(ref, false, entry.getTimeInSeconds(this.currentTime) * timeResource.getTimeDilationModifier(), chain.getType(), context);
} finally {
context.deinitEntry(chain, entry, this.entity);
}
chain.setOperationCounter(maxOperations);
}
}
}
}
private void doTickChain(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain) {
ObjectList<InteractionSyncData> interactionData = this.tempSyncDataList;
interactionData.clear();
RootInteraction root = chain.getRootInteraction();
int maxOperations = root.getOperationMax();
int currentOp = chain.getOperationCounter();
int baseOpIndex = chain.getOperationIndex();
int callDepth = chain.getCallDepth();
if (chain.consumeFirstRun()) {
if (chain.getForkedChainId() == null) {
chain.setTimeShift(this.getGlobalTimeShift(chain.getType()));
} else {
InteractionChain parent = (InteractionChain)this.chains.get(chain.getChainId());
chain.setFirstRun(parent != null && parent.isFirstRun());
}
} else {
chain.setTimeShift(0.0F);
}
if (!chain.getContext().getEntity().isValid()) {
throw new ChainCancelledException(chain.getServerState());
} else {
while(true) {
Operation simOp = !this.hasRemoteClient ? root.getOperation(chain.getSimulatedOperationCounter()) : null;
WaitForDataFrom simWaitFrom = simOp != null ? simOp.getWaitForDataFrom() : null;
long tickTime = this.currentTime;
if (!this.hasRemoteClient && simWaitFrom != WaitForDataFrom.Server) {
this.simulationTick(ref, chain, tickTime);
}
interactionData.add(this.serverTick(ref, chain, tickTime));
if (!chain.getContext().getEntity().isValid() && chain.getServerState() != InteractionState.Finished && chain.getServerState() != InteractionState.Failed) {
throw new ChainCancelledException(chain.getServerState());
}
if (!this.hasRemoteClient && simWaitFrom == WaitForDataFrom.Server) {
this.simulationTick(ref, chain, tickTime);
}
if (!this.hasRemoteClient) {
if (chain.getRootInteraction() != chain.getSimulatedRootInteraction()) {
String var14 = chain.getRootInteraction().getId();
throw new IllegalStateException("Simulation and server tick are not in sync (root interaction).\n" + var14 + " vs " + String.valueOf(chain.getSimulatedRootInteraction()));
}
if (chain.getOperationCounter() != chain.getSimulatedOperationCounter()) {
String var10002 = chain.getRootInteraction().getId();
throw new IllegalStateException("Simulation and server tick are not in sync (operation position).\nRoot: " + var10002 + "\nCounter: " + chain.getOperationCounter() + " vs " + chain.getSimulatedOperationCounter() + "\nIndex: " + chain.getOperationIndex());
}
}
if (callDepth != chain.getCallDepth()) {
callDepth = chain.getCallDepth();
root = chain.getRootInteraction();
maxOperations = root.getOperationMax();
} else if (currentOp == chain.getOperationCounter()) {
break;
}
chain.nextOperationIndex();
currentOp = chain.getOperationCounter();
if (currentOp >= maxOperations) {
while(callDepth > 0) {
chain.popRoot();
callDepth = chain.getCallDepth();
currentOp = chain.getOperationCounter();
root = chain.getRootInteraction();
maxOperations = root.getOperationMax();
if (currentOp < maxOperations || callDepth == 0) {
break;
}
}
if (callDepth == 0 && currentOp >= maxOperations) {
break;
}
}
}
chain.updateServerState();
if (!this.hasRemoteClient) {
chain.updateSimulatedState();
}
if (chain.requiresClient()) {
this.sendSyncPacket(chain, baseOpIndex, interactionData);
}
}
}
@Nullable
private InteractionSyncData serverTick(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain, long tickTime) {
assert this.commandBuffer != null;
RootInteraction root = chain.getRootInteraction();
Operation operation = root.getOperation(chain.getOperationCounter());
assert operation != null;
InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getOperationIndex());
InteractionSyncData returnData = null;
boolean wasWrong = entry.consumeDesyncFlag();
if (entry.getClientState() == null) {
wasWrong |= !entry.setClientState(chain.removeInteractionSyncData(chain.getOperationIndex()));
}
if (wasWrong) {
returnData = entry.getServerState();
chain.flagDesync();
chain.clearInteractionSyncData(chain.getOperationIndex());
}
TimeResource timeResource = (TimeResource)this.commandBuffer.getResource(TimeResource.getResourceType());
float tickTimeDilation = timeResource.getTimeDilationModifier();
if (operation.getWaitForDataFrom() == WaitForDataFrom.Client && entry.getClientState() == null) {
if (this.waitingForClient(ref)) {
return null;
} else {
if (entry.getWaitingForSyncData() == 0L) {
entry.setWaitingForSyncData(this.currentTime);
}
long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - entry.getWaitingForSyncData());
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Wait for interaction clientData: %d, %s, %s", chain.getOperationIndex(), entry, waitMillis);
}
long threshold = this.getOperationTimeoutThreshold();
if (tickTimeDilation == 1.0F && waitMillis > threshold) {
throw new RuntimeException("Client took too long to send clientData! Millis: " + waitMillis + ", Threshold: " + threshold + ",\nChain: " + String.valueOf(chain) + ",\nEntry: " + chain.getOperationIndex() + ", " + String.valueOf(entry) + ",\nWaiting for data from: " + String.valueOf(operation.getWaitForDataFrom()));
} else {
if (entry.consumeSendInitial() || wasWrong) {
returnData = entry.getServerState();
}
return returnData;
}
}
} else {
int serverDataHashCode = entry.getServerDataHashCode();
InteractionContext context = chain.getContext();
float time = entry.getTimeInSeconds(tickTime);
boolean firstRun = false;
if (entry.getTimestamp() == 0L) {
time = chain.getTimeShift();
entry.setTimestamp(tickTime, time);
firstRun = true;
}
time *= tickTimeDilation;
try {
context.initEntry(chain, entry, this.entity);
operation.tick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler);
} finally {
context.deinitEntry(chain, entry, this.entity);
}
InteractionSyncData serverData = entry.getServerState();
if (firstRun || serverDataHashCode != entry.getServerDataHashCode()) {
returnData = serverData;
}
try {
context.initEntry(chain, entry, this.entity);
operation.handle(ref, firstRun, time, chain.getType(), context);
} finally {
context.deinitEntry(chain, entry, this.entity);
}
this.removeInteractionIfFinished(ref, chain, entry);
return returnData;
}
}
private void removeInteractionIfFinished(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain, @Nonnull InteractionEntry entry) {
if (chain.getOperationIndex() == entry.getIndex() && entry.getServerState().state != InteractionState.NotFinished) {
chain.setFinalState(entry.getServerState().state);
}
if (entry.getServerState().state != InteractionState.NotFinished) {
LOGGER.at(Level.FINE).log("Server finished interaction: %d, %s", entry.getIndex(), entry);
if (!chain.requiresClient() || entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished) {
LOGGER.at(Level.FINER).log("Remove Interaction: %d, %s", entry.getIndex(), entry);
chain.removeInteractionEntry(this, entry.getIndex());
}
} else if (entry.getClientState() != null && entry.getClientState().state != InteractionState.NotFinished && !this.waitingForClient(ref)) {
if (entry.getWaitingForServerFinished() == 0L) {
entry.setWaitingForServerFinished(this.currentTime);
}
long waitMillis = TimeUnit.NANOSECONDS.toMillis(this.currentTime - entry.getWaitingForServerFinished());
HytaleLogger.Api context = LOGGER.at(Level.FINE);
if (context.isEnabled()) {
context.log("Client finished interaction but server hasn't! %s, %d, %s, %s", entry.getClientState().state, entry.getIndex(), entry, waitMillis);
}
long threshold = this.getOperationTimeoutThreshold();
if (waitMillis > threshold) {
HytaleLogger.Api ctx = LOGGER.at(Level.SEVERE);
if (ctx.isEnabled()) {
ctx.log("Client finished interaction earlier than server! %d, %s", entry.getIndex(), entry);
}
}
}
}
private void simulationTick(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain, long tickTime) {
assert this.commandBuffer != null;
RootInteraction rootInteraction = chain.getRootInteraction();
Operation operation = rootInteraction.getOperation(chain.getSimulatedOperationCounter());
if (operation == null) {
throw new IllegalStateException("Failed to find operation during simulation tick of chain '" + rootInteraction.getId() + "'");
} else {
InteractionEntry entry = chain.getOrCreateInteractionEntry(chain.getClientOperationIndex());
InteractionContext context = chain.getContext();
entry.setUseSimulationState(true);
try {
context.initEntry(chain, entry, this.entity);
float time = entry.getTimeInSeconds(tickTime);
boolean firstRun = false;
if (entry.getTimestamp() == 0L) {
time = chain.getTimeShift();
entry.setTimestamp(tickTime, time);
firstRun = true;
}
TimeResource timeResource = (TimeResource)this.commandBuffer.getResource(TimeResource.getResourceType());
float tickTimeDilation = timeResource.getTimeDilationModifier();
time *= tickTimeDilation;
operation.simulateTick(ref, this.entity, firstRun, time, chain.getType(), context, this.cooldownHandler);
} finally {
context.deinitEntry(chain, entry, this.entity);
entry.setUseSimulationState(false);
}
if (!entry.setClientState(entry.getSimulationState())) {
throw new RuntimeException("Simulation failed");
} else {
this.removeInteractionIfFinished(ref, chain, entry);
}
}
}
private boolean syncStart(@Nonnull Ref<EntityStore> ref, @Nonnull SyncInteractionChain packet) {
assert this.commandBuffer != null;
int index = packet.chainId;
if (!packet.initial) {
if (packet.forkedId == null) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Got syncStart for %d-%s but packet wasn't the first.", index, packet.forkedId);
}
}
return true;
} else if (packet.forkedId != null) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Can't start a forked chain from the client: %d %s", index, packet.forkedId);
}
return true;
} else {
InteractionType type = packet.interactionType;
if (index <= 0) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Invalid client chainId! Got %d but client id's should be > 0", index);
}
this.sendCancelPacket(index, packet.forkedId);
return true;
} else if (index <= this.lastClientChainId) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Invalid client chainId! The last clientChainId was %d but just got %d", this.lastClientChainId, index);
}
this.sendCancelPacket(index, packet.forkedId);
return true;
} else {
UUID proxyId = packet.data.proxyId;
InteractionContext context;
if (!UUIDUtil.isEmptyOrNull(proxyId)) {
World world = ((EntityStore)this.commandBuffer.getExternalData()).getWorld();
Ref<EntityStore> proxyTarget = world.getEntityStore().getRefFromUUID(proxyId);
if (proxyTarget == null) {
if (this.packetQueueTime != 0L && this.currentTime - this.packetQueueTime > TimeUnit.MILLISECONDS.toNanos(this.getOperationTimeoutThreshold()) / 2L) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Proxy entity never spawned");
}
this.sendCancelPacket(index, packet.forkedId);
return true;
}
return false;
}
context = InteractionContext.forProxyEntity(this, this.entity, proxyTarget);
} else {
context = InteractionContext.forInteraction(this, ref, type, packet.equipSlot, this.commandBuffer);
}
String rootInteractionId = context.getRootInteractionId(type);
if (rootInteractionId == null) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Missing root interaction: %d, %s, %s", index, this.entity.getInventory().getItemInHand(), type);
}
this.sendCancelPacket(index, packet.forkedId);
return true;
} else {
RootInteraction rootInteraction = RootInteraction.getRootInteractionOrUnknown(rootInteractionId);
if (rootInteraction == null) {
return false;
} else if (!this.applyRules(context, packet.data, type, rootInteraction)) {
return false;
} else {
Inventory entityInventory = this.entity.getInventory();
ItemStack itemInHand = entityInventory.getActiveHotbarItem();
ItemStack utilityItem = entityInventory.getUtilityItem();
String serverItemInHandId = itemInHand != null ? itemInHand.getItemId() : null;
String serverUtilityItemId = utilityItem != null ? utilityItem.getItemId() : null;
if (packet.activeHotbarSlot != entityInventory.getActiveHotbarSlot()) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Active slot miss match: %d, %d != %d, %s, %s, %s", index, entityInventory.getActiveHotbarSlot(), packet.activeHotbarSlot, serverItemInHandId, packet.itemInHandId, type);
}
this.sendCancelPacket(index, packet.forkedId);
if (this.playerRef != null) {
this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, entityInventory.getActiveHotbarSlot()));
}
return true;
} else if (packet.activeUtilitySlot != entityInventory.getActiveUtilitySlot()) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("Active slot miss match: %d, %d != %d, %s, %s, %s", index, entityInventory.getActiveUtilitySlot(), packet.activeUtilitySlot, serverItemInHandId, packet.itemInHandId, type);
}
this.sendCancelPacket(index, packet.forkedId);
if (this.playerRef != null) {
this.playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-5, entityInventory.getActiveUtilitySlot()));
}
return true;
} else if (!Objects.equals(serverItemInHandId, packet.itemInHandId)) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("ItemInHand miss match: %d, %s, %s, %s", index, serverItemInHandId, packet.itemInHandId, type);
}
this.sendCancelPacket(index, packet.forkedId);
return true;
} else if (!Objects.equals(serverUtilityItemId, packet.utilityItemId)) {
HytaleLogger.Api ctx = LOGGER.at(Level.FINE);
if (ctx.isEnabled()) {
ctx.log("UtilityItem miss match: %d, %s, %s, %s", index, serverUtilityItemId, packet.utilityItemId, type);
}
this.sendCancelPacket(index, packet.forkedId);
return true;
} else if (this.isOnCooldown(ref, type, rootInteraction, true)) {
return false;
} else {
InteractionChain chain = this.initChain(packet.data, type, context, rootInteraction, (Runnable)null, true);
chain.setChainId(index);
this.sync(ref, chain, packet);
World world = ((EntityStore)this.commandBuffer.getExternalData()).getWorld();
if (packet.data.blockPosition != null) {
BlockPosition targetBlock = world.getBaseBlock(packet.data.blockPosition);
context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, targetBlock);
context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, packet.data.blockPosition);
}
if (packet.data.entityId >= 0) {
EntityStore entityComponentStore = world.getEntityStore();
Ref<EntityStore> entityReference = entityComponentStore.getRefFromNetworkId(packet.data.entityId);
if (entityReference != null) {
context.getMetaStore().putMetaObject(Interaction.TARGET_ENTITY, entityReference);
}
}
if (packet.data.targetSlot != -2147483648) {
context.getMetaStore().putMetaObject(Interaction.TARGET_SLOT, packet.data.targetSlot);
}
if (packet.data.hitLocation != null) {
Vector3f hit = packet.data.hitLocation;
context.getMetaStore().putMetaObject(Interaction.HIT_LOCATION, new Vector4d((double)hit.x, (double)hit.y, (double)hit.z, 1.0));
}
if (packet.data.hitDetail != null) {
context.getMetaStore().putMetaObject(Interaction.HIT_DETAIL, packet.data.hitDetail);
}
this.lastClientChainId = index;
if (!this.tickChain(chain)) {
chain.setPreTicked(true);
this.chains.put(index, chain);
}
return true;
}
}
}
}
}
}
public void sync(@Nonnull Ref<EntityStore> ref, @Nonnull ChainSyncStorage chainSyncStorage, @Nonnull SyncInteractionChain packet) {
assert this.commandBuffer != null;
if (packet.newForks != null) {
for(SyncInteractionChain fork : packet.newForks) {
chainSyncStorage.syncFork(ref, this, fork);
}
}
if (packet.interactionData == null) {
chainSyncStorage.setClientState(packet.state);
} else {
for(int i = 0; i < packet.interactionData.length; ++i) {
InteractionSyncData syncData = packet.interactionData[i];
if (syncData != null) {
int index = packet.operationBaseIndex + i;
if (!chainSyncStorage.isSyncDataOutOfOrder(index)) {
InteractionEntry interaction = chainSyncStorage.getInteraction(index);
if (interaction != null && chainSyncStorage instanceof InteractionChain) {
InteractionChain interactionChain = (InteractionChain)chainSyncStorage;
if (interaction.getClientState() != null && interaction.getClientState().state != InteractionState.NotFinished && syncData.state == InteractionState.NotFinished || !interaction.setClientState(syncData)) {
chainSyncStorage.clearInteractionSyncData(index);
interaction.flagDesync();
interactionChain.flagDesync();
return;
}
chainSyncStorage.updateSyncPosition(index);
HytaleLogger.Api context = LOGGER.at(Level.FINEST);
if (context.isEnabled()) {
TimeResource timeResource = (TimeResource)this.commandBuffer.getResource(TimeResource.getResourceType());
float tickTimeDilation = timeResource.getTimeDilationModifier();
context.log("%d, %d: Time (Sync) - Server: %s vs Client: %s", packet.chainId, index, interaction.getTimeInSeconds(this.currentTime) * tickTimeDilation, interaction.getClientState().progress);
}
this.removeInteractionIfFinished(ref, interactionChain, interaction);
} else {
chainSyncStorage.putInteractionSyncData(index, syncData);
}
}
}
}
int last = packet.operationBaseIndex + packet.interactionData.length;
chainSyncStorage.clearInteractionSyncData(last);
chainSyncStorage.setClientState(packet.state);
}
}
public boolean canRun(@Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction) {
return this.canRun(type, (short)-1, rootInteraction);
}
public boolean canRun(@Nonnull InteractionType type, short equipSlot, @Nonnull RootInteraction rootInteraction) {
return applyRules((InteractionChainData)null, type, equipSlot, rootInteraction, this.chains, (List)null);
}
public boolean applyRules(@Nonnull InteractionContext context, @Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull RootInteraction rootInteraction) {
List<InteractionChain> chainsToCancel = new ObjectArrayList<InteractionChain>();
if (!applyRules(data, type, context.getHeldItemSlot(), rootInteraction, this.chains, chainsToCancel)) {
return false;
} else {
for(InteractionChain interactionChain : chainsToCancel) {
this.cancelChains(interactionChain);
}
return true;
}
}
public void cancelChains(@Nonnull InteractionChain chain) {
chain.setServerState(InteractionState.Failed);
chain.setClientState(InteractionState.Failed);
this.sendCancelPacket(chain);
for(InteractionChain fork : chain.getForkedChains().values()) {
this.cancelChains(fork);
}
}
private static boolean applyRules(@Nullable InteractionChainData data, @Nonnull InteractionType type, int heldItemSlot, @Nullable RootInteraction rootInteraction, @Nonnull Map<?, InteractionChain> chains, @Nullable List<InteractionChain> chainsToCancel) {
if (!chains.isEmpty() && rootInteraction != null) {
for(InteractionChain chain : chains.values()) {
if ((chain.getForkedChainId() == null || chain.isPredicted()) && (data == null || Objects.equals(chain.getChainData().proxyId, data.proxyId)) && (type != InteractionType.Equipped || chain.getType() != InteractionType.Equipped || chain.getContext().getHeldItemSlot() == heldItemSlot)) {
if (chain.getServerState() == InteractionState.NotFinished) {
RootInteraction currentRoot = chain.getRootInteraction();
Operation currentOp = currentRoot.getOperation(chain.getOperationCounter());
if (rootInteraction.getRules().validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) {
if (chainsToCancel != null) {
chainsToCancel.add(chain);
}
} else if (currentOp != null && currentOp.getRules() != null && rootInteraction.getRules().validateInterrupts(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) {
if (chainsToCancel != null) {
chainsToCancel.add(chain);
}
} else {
if (rootInteraction.getRules().validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentRoot.getData().getTags(), currentRoot.getRules())) {
return false;
}
if (currentOp != null && currentOp.getRules() != null && rootInteraction.getRules().validateBlocked(type, rootInteraction.getData().getTags(), chain.getType(), currentOp.getTags(), currentOp.getRules())) {
return false;
}
}
}
if ((chainsToCancel == null || chainsToCancel.isEmpty()) && !applyRules(data, type, heldItemSlot, rootInteraction, chain.getForkedChains(), chainsToCancel)) {
return false;
}
}
}
return true;
} else {
return true;
}
}
public boolean tryStartChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction) {
InteractionChain chain = this.initChain(type, context, rootInteraction, false);
if (!this.applyRules(context, chain.getChainData(), type, rootInteraction)) {
return false;
} else {
this.executeChain(ref, commandBuffer, chain);
return true;
}
}
public void startChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction) {
InteractionChain chain = this.initChain(type, context, rootInteraction, false);
this.executeChain(ref, commandBuffer, chain);
}
@Nonnull
public InteractionChain initChain(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, boolean forceRemoteSync) {
return this.initChain(type, context, rootInteraction, -1, (BlockPosition)null, forceRemoteSync);
}
@Nonnull
public InteractionChain initChain(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, int entityId, @Nullable BlockPosition blockPosition, boolean forceRemoteSync) {
InteractionChainData data = new InteractionChainData(entityId, UUIDUtil.EMPTY_UUID, (Vector3f)null, (String)null, blockPosition, -2147483648, (Vector3f)null);
return this.initChain(data, type, context, rootInteraction, (Runnable)null, forceRemoteSync);
}
@Nonnull
public InteractionChain initChain(@Nonnull InteractionChainData data, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull RootInteraction rootInteraction, @Nullable Runnable onCompletion, boolean forceRemoteSync) {
return new InteractionChain(type, context, data, rootInteraction, onCompletion, forceRemoteSync || !this.hasRemoteClient);
}
public void queueExecuteChain(@Nonnull InteractionChain chain) {
this.chainStartQueue.add(chain);
}
public void executeChain(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionChain chain) {
this.commandBuffer = commandBuffer;
this.executeChain0(ref, chain);
this.commandBuffer = null;
}
private void executeChain0(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionChain chain) {
if (this.isOnCooldown(ref, chain.getType(), chain.getInitialRootInteraction(), false)) {
chain.setServerState(InteractionState.Failed);
chain.setClientState(InteractionState.Failed);
} else {
int index = --this.lastServerChainId;
if (index >= 0) {
index = this.lastServerChainId = -1;
}
chain.setChainId(index);
if (!this.tickChain(chain)) {
LOGGER.at(Level.FINE).log("Add Chain: %d, %s", index, chain);
chain.setPreTicked(true);
this.chains.put(index, chain);
}
}
}
private boolean isOnCooldown(@Nonnull Ref<EntityStore> ref, @Nonnull InteractionType type, @Nonnull RootInteraction root, boolean remote) {
assert this.commandBuffer != null;
InteractionCooldown cooldown = root.getCooldown();
String cooldownId = root.getId();
float cooldownTime = InteractionTypeUtils.getDefaultCooldown(type);
float[] cooldownChargeTimes = DEFAULT_CHARGE_TIMES;
boolean interruptRecharge = false;
if (cooldown != null) {
cooldownTime = cooldown.cooldown;
if (cooldown.chargeTimes != null && cooldown.chargeTimes.length > 0) {
cooldownChargeTimes = cooldown.chargeTimes;
}
if (cooldown.cooldownId != null) {
cooldownId = cooldown.cooldownId;
}
if (cooldown.interruptRecharge) {
interruptRecharge = true;
}
if (cooldown.clickBypass && remote) {
this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge);
return false;
}
}
Player playerComponent = (Player)this.commandBuffer.getComponent(ref, Player.getComponentType());
GameMode gameMode = playerComponent != null ? playerComponent.getGameMode() : GameMode.Adventure;
RootInteractionSettings settings = (RootInteractionSettings)root.getSettings().get(gameMode);
if (settings != null && settings.allowSkipChainOnClick && remote) {
this.cooldownHandler.resetCooldown(cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge);
return false;
} else {
return this.cooldownHandler.isOnCooldown(root, cooldownId, cooldownTime, cooldownChargeTimes, interruptRecharge);
}
}
public void tryRunHeldInteraction(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type) {
this.tryRunHeldInteraction(ref, commandBuffer, type, (short)-1);
}
public void tryRunHeldInteraction(@Nonnull Ref<EntityStore> ref, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull InteractionType type, short equipSlot) {
Inventory inventory = this.entity.getInventory();
ItemStack itemStack;
switch (type) {
case Held:
itemStack = inventory.getItemInHand();
break;
case HeldOffhand:
itemStack = inventory.getUtilityItem();
break;
case Equipped:
if (equipSlot == -1) {
throw new IllegalArgumentException();
}
itemStack = inventory.getArmor().getItemStack(equipSlot);
break;
default:
throw new IllegalArgumentException();
}
if (itemStack != null && !itemStack.isEmpty()) {
String rootId = (String)itemStack.getItem().getInteractions().get(type);
if (rootId != null) {
RootInteraction root = (RootInteraction)RootInteraction.getAssetMap().getAsset(rootId);
if (root != null && this.canRun(type, equipSlot, root)) {
InteractionContext context = InteractionContext.forInteraction(this, ref, type, equipSlot, commandBuffer);
this.startChain(ref, commandBuffer, type, context, root);
}
}
}
}
public void sendSyncPacket(@Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List<InteractionSyncData> interactionData) {
if (!chain.hasSentInitial() || interactionData != null && !ListUtil.emptyOrAllNull(interactionData) || !chain.getNewForks().isEmpty()) {
if (this.playerRef != null) {
SyncInteractionChain packet = makeSyncPacket(chain, operationBaseIndex, interactionData);
this.syncPackets.add(packet);
}
}
}
@Nonnull
private static SyncInteractionChain makeSyncPacket(@Nonnull InteractionChain chain, int operationBaseIndex, @Nullable List<InteractionSyncData> interactionData) {
SyncInteractionChain[] forks = null;
List<InteractionChain> newForks = chain.getNewForks();
if (!newForks.isEmpty()) {
forks = new SyncInteractionChain[newForks.size()];
for(int i = 0; i < newForks.size(); ++i) {
InteractionChain fc = (InteractionChain)newForks.get(i);
forks[i] = makeSyncPacket(fc, 0, (List)null);
}
newForks.clear();
}
SyncInteractionChain packet = new SyncInteractionChain(0, 0, 0, (String)null, (String)null, (String)null, !chain.hasSentInitial(), false, chain.hasSentInitial() ? -2147483648 : RootInteraction.getRootInteractionIdOrUnknown(chain.getInitialRootInteraction().getId()), chain.getType(), chain.getContext().getHeldItemSlot(), chain.getChainId(), chain.getForkedChainId(), chain.getChainData(), chain.getServerState(), forks, operationBaseIndex, interactionData == null ? null : (InteractionSyncData[])interactionData.toArray((x$0) -> new InteractionSyncData[x$0]));
chain.setSentInitial(true);
return packet;
}
private void sendCancelPacket(@Nonnull InteractionChain chain) {
this.sendCancelPacket(chain.getChainId(), chain.getForkedChainId());
}
public void sendCancelPacket(int chainId, @Nonnull ForkedChainId forkedChainId) {
if (this.playerRef != null) {
this.playerRef.getPacketHandler().writeNoCache(new CancelInteractionChain(chainId, forkedChainId));
}
}
public void clear() {
this.forEachInteraction((chain, _i, _a) -> {
chain.setServerState(InteractionState.Failed);
chain.setClientState(InteractionState.Failed);
this.sendCancelPacket(chain);
return null;
}, (Object)null);
this.chainStartQueue.clear();
}
public void clearAllGlobalTimeShift(float dt) {
if (this.timeShiftsDirty) {
boolean clearFlag = true;
for(int i = 0; i < this.globalTimeShift.length; ++i) {
if (!this.globalTimeShiftDirty[i]) {
this.globalTimeShift[i] = 0.0F;
} else {
clearFlag = false;
float[] var10000 = this.globalTimeShift;
var10000[i] += dt;
}
}
Arrays.fill(this.globalTimeShiftDirty, false);
if (clearFlag) {
this.timeShiftsDirty = false;
}
}
}
public void setGlobalTimeShift(@Nonnull InteractionType type, float shift) {
if (shift < 0.0F) {
throw new IllegalArgumentException("Can't shift backwards");
} else {
this.globalTimeShift[type.ordinal()] = shift;
this.globalTimeShiftDirty[type.ordinal()] = true;
this.timeShiftsDirty = true;
}
}
public float getGlobalTimeShift(@Nonnull InteractionType type) {
return this.globalTimeShift[type.ordinal()];
}
public <T> T forEachInteraction(@Nonnull TriFunction<InteractionChain, Interaction, T, T> func, @Nonnull T val) {
return (T)forEachInteraction(this.chains, func, val);
}
private static <T> T forEachInteraction(@Nonnull Map<?, InteractionChain> chains, @Nonnull TriFunction<InteractionChain, Interaction, T, T> func, @Nonnull T val) {
if (chains.isEmpty()) {
return val;
} else {
for(InteractionChain chain : chains.values()) {
Operation operation = chain.getRootInteraction().getOperation(chain.getOperationCounter());
if (operation != null) {
operation = operation.getInnerOperation();
if (operation instanceof Interaction) {
Interaction interaction = (Interaction)operation;
val = func.apply(chain, interaction, val);
}
}
val = (T)forEachInteraction(chain.getForkedChains(), func, val);
}
return val;
}
}
public void walkChain(@Nonnull Ref<EntityStore> ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
this.walkChain(ref, collector, type, (RootInteraction)null, componentAccessor);
}
public void walkChain(@Nonnull Ref<EntityStore> ref, @Nonnull Collector collector, @Nonnull InteractionType type, @Nullable RootInteraction rootInteraction, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
walkChain(collector, type, InteractionContext.forInteraction(this, ref, type, componentAccessor), rootInteraction);
}
public static void walkChain(@Nonnull Collector collector, @Nonnull InteractionType type, @Nonnull InteractionContext context, @Nullable RootInteraction rootInteraction) {
if (rootInteraction == null) {
String rootInteractionId = context.getRootInteractionId(type);
if (rootInteractionId == null) {
String var5 = String.valueOf(type);
throw new IllegalArgumentException("No interaction ID found for " + var5 + ", " + String.valueOf(context));
}
rootInteraction = (RootInteraction)RootInteraction.getAssetMap().getAsset(rootInteractionId);
}
if (rootInteraction == null) {
String var10002 = String.valueOf(type);
throw new IllegalArgumentException("No interactions are defined for " + var10002 + ", " + String.valueOf(context));
} else {
collector.start();
collector.into(context, (Interaction)null);
walkInteractions(collector, context, CollectorTag.ROOT, rootInteraction.getInteractionIds());
collector.outof();
collector.finished();
}
}
public static boolean walkInteractions(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nonnull String[] interactionIds) {
for(String id : interactionIds) {
if (walkInteraction(collector, context, tag, id)) {
return true;
}
}
return false;
}
public static boolean walkInteraction(@Nonnull Collector collector, @Nonnull InteractionContext context, @Nonnull CollectorTag tag, @Nullable String id) {
if (id == null) {
return false;
} else {
Interaction interaction = (Interaction)Interaction.getAssetMap().getAsset(id);
if (interaction == null) {
throw new IllegalArgumentException("Failed to find interaction: " + id);
} else if (collector.collect(tag, context, interaction)) {
return true;
} else {
collector.into(context, interaction);
interaction.walk(collector, context);
collector.outof();
return false;
}
}
}
public ObjectList<SyncInteractionChain> getSyncPackets() {
return this.syncPackets;
}
@Nonnull
public Component<EntityStore> clone() {
InteractionManager manager = new InteractionManager(this.entity, this.playerRef, this.interactionSimulationHandler);
manager.copyFrom(this);
return manager;
}
public static class ChainCancelledException extends RuntimeException {
@Nonnull
private final InteractionState state;
public ChainCancelledException(@Nonnull InteractionState state) {
this.state = state;
}
}
}