diff --git a/.gitignore b/.gitignore index b722f888..efdd86ac 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ $RECYCLE.BIN/ *.lnk .gradle +.gradle-user/ build/ # Ignore Gradle GUI config diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d303cbca..dc4a38cc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { compileOnly(project(":logging")) } -val coreVersion = "0.4.1-SNAPSHOT" +val coreVersion = "0.4.2-SNAPSHOT" val isRelease = gradle.startParameter.taskNames.any { it.contains("buildRelease") diff --git a/core/src/main/java/games/cubi/raycastedantiesp/core/players/NettyData.java b/core/src/main/java/games/cubi/raycastedantiesp/core/players/NettyData.java index 0e6048bc..d9913984 100644 --- a/core/src/main/java/games/cubi/raycastedantiesp/core/players/NettyData.java +++ b/core/src/main/java/games/cubi/raycastedantiesp/core/players/NettyData.java @@ -3,6 +3,7 @@ import games.cubi.logs.Logger; import games.cubi.raycastedantiesp.core.utils.*; import games.cubi.raycastedantiesp.core.utils.Packet.Packets; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; @@ -10,16 +11,18 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; +import static games.cubi.raycastedantiesp.core.locatables.NettyEntityLocatable.NO_VEHICLE; /** * Per-player mutable state intended for Netty-side packet tracking and deferred reconciliation. */ public class NettyData implements Clearable { + private static final int DEFAULT_MAP_SIZE = 16; // // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // START Leash tracking: // - private final Int2ObjectArrayMap unresolvedLeashedEntityIDsByHolderID = new Int2ObjectArrayMap<>(16); // shot in the dark guess at capacity here. Can't be the more generic Int2ObjectMap because that doesn't expose a fast iterator. + private final Int2ObjectArrayMap unresolvedLeashedEntityIDsByHolderID = new Int2ObjectArrayMap<>(DEFAULT_MAP_SIZE); public void addUnresolvedLeash(int holderEntityID, int leashedEntityID) { unresolvedLeashedEntityIDsByHolderID.compute(holderEntityID, (ignored, existing) -> { @@ -52,11 +55,11 @@ public void removeUnresolvedLeashedEntityFromAll(int leashedEntityID) { while (iterator.hasNext()) { Int2ObjectMap.Entry entry = iterator.next(); int[] existing = entry.getValue(); - if (!IntArrayList.contains(existing, leashedEntityID)) { + if (!PrimitiveIntArrayList.contains(existing, leashedEntityID)) { continue; } - int[] updated = IntArrayList.remove(existing, leashedEntityID); - if (IntArrayList.isEmpty(updated)) { + int[] updated = PrimitiveIntArrayList.remove(existing, leashedEntityID); + if (PrimitiveIntArrayList.isEmpty(updated)) { iterator.remove(); continue; } @@ -68,6 +71,98 @@ public void removeUnresolvedLeashedEntityFromAll(int leashedEntityID) { // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // + // + // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // START Passenger tracking: + // + /** + * Latest unresolved full passenger list for a vehicle. + * This is only needed while at least one referenced passenger or the vehicle-side reconciliation has still not been spawned in for the client. The actual client deals with this somehow but we need the entity to have been spawned in before we can register the passenger relationship. + */ + private final Int2ObjectArrayMap unresolvedPassengerIDsByVehicleID = new Int2ObjectArrayMap<>(DEFAULT_MAP_SIZE); + /** + * Reverse lookup for unresolved passenger relationships. + * Lets a later passenger spawn discover which vehicle most recently claimed it as a passenger. + * This may grow larger than {@link #unresolvedPassengerIDsByVehicleID} if vehicles have several passengers, so it's an open hash map. + */ + private final Int2IntOpenHashMap unresolvedVehicleIDsByPassengerID = new Int2IntOpenHashMap(DEFAULT_MAP_SIZE); + + { + unresolvedVehicleIDsByPassengerID.defaultReturnValue(NO_VEHICLE); + } + + public int[] getUnresolvedPassengers(int vehicleEntityID) { + return PrimitiveIntArrayList.getCopyOrNull(unresolvedPassengerIDsByVehicleID.get(vehicleEntityID)); + } + + /** + * Replaces any previous unresolved passenger state for this vehicle. + * The passenger packet is authoritative, so the latest packet wins. + */ + public void setUnresolvedPassengers(int vehicleEntityID, int[] passengerIDs) { + consumeUnresolvedPassengers(vehicleEntityID); + if (PrimitiveIntArrayList.isEmpty(passengerIDs)) { + return; + } + int[] copiedPassengerIDs = passengerIDs.clone(); + unresolvedPassengerIDsByVehicleID.put(vehicleEntityID, copiedPassengerIDs); + for (int passengerID : copiedPassengerIDs) { + int previousVehicleID = unresolvedVehicleIDsByPassengerID.put(passengerID, vehicleEntityID); + if (previousVehicleID != NO_VEHICLE && previousVehicleID != vehicleEntityID) { + removePassengerFromUnresolvedVehicle(previousVehicleID, passengerID); + } + } + } + + public int[] consumeUnresolvedPassengers(int vehicleEntityID) { + int[] existing = unresolvedPassengerIDsByVehicleID.remove(vehicleEntityID); + if (PrimitiveIntArrayList.isEmpty(existing)) { + return existing; + } + for (int passengerID : existing) { + if (unresolvedVehicleIDsByPassengerID.get(passengerID) == vehicleEntityID) { + unresolvedVehicleIDsByPassengerID.remove(passengerID); + } + } + return existing; + } + + public int getUnresolvedVehicleForPassenger(int passengerEntityID) { + return unresolvedVehicleIDsByPassengerID.get(passengerEntityID); + } + + public boolean removeUnresolvedPassengerLink(int passengerEntityID, int vehicleEntityID) { + if (unresolvedVehicleIDsByPassengerID.get(passengerEntityID) != vehicleEntityID) { + return false; + } + unresolvedVehicleIDsByPassengerID.remove(passengerEntityID); + removePassengerFromUnresolvedVehicle(vehicleEntityID, passengerEntityID); + return true; + } + + public int consumeUnresolvedVehicleForPassenger(int passengerEntityID) { + int vehicleEntityID = unresolvedVehicleIDsByPassengerID.remove(passengerEntityID); + if (vehicleEntityID == NO_VEHICLE) { + return NO_VEHICLE; + } + removePassengerFromUnresolvedVehicle(vehicleEntityID, passengerEntityID); + return vehicleEntityID; + } + + private void removePassengerFromUnresolvedVehicle(int vehicleEntityID, int passengerEntityID) { + unresolvedPassengerIDsByVehicleID.computeIfPresent(vehicleEntityID, (ignored, existing) -> { + if (!PrimitiveIntArrayList.contains(existing, passengerEntityID)) { + return existing; + } + int[] updated = PrimitiveIntArrayList.remove(existing, passengerEntityID); + return PrimitiveIntArrayList.isEmpty(updated) ? null : updated; + }); + } + // + // END Passenger tracking. + // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // + // // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // START Netty entity spawn task queue: @@ -143,6 +238,8 @@ public void evictOldPendingPostSpawnTasks(int currentTick) { @Override public void clear() { unresolvedLeashedEntityIDsByHolderID.clear(); + unresolvedPassengerIDsByVehicleID.clear(); + unresolvedVehicleIDsByPassengerID.clear(); pendingPostEntitySpawnTasksByEntityID.clear(); } } diff --git a/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PacketEntityViewController.java b/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PacketEntityViewController.java index 36e0ae1a..845d6a00 100644 --- a/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PacketEntityViewController.java +++ b/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PacketEntityViewController.java @@ -10,6 +10,7 @@ import games.cubi.raycastedantiesp.core.utils.PrimitiveIntArrayList; import games.cubi.raycastedantiesp.core.utils.Packet; import games.cubi.raycastedantiesp.core.view.EntityView; +import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.ArrayList; import java.util.UUID; @@ -194,38 +195,43 @@ protected boolean handleEntityEffect(P packet, int entityID, PlayerData playerDa * @return Whether or not to cancel the packet event. true to cancel, false to do nothing. */ protected boolean handleEntityPassengers(int entityID, int[] passengers, PlayerData playerData, int currentTick) { - NettyEntityLocatable entity = playerData.entityFromID(entityID); - if (entity == null) { - queuePassengerRetry(playerData, entityID, entityID, passengers, currentTick); + NettyEntityLocatable vehicle = playerData.entityFromID(entityID); + int[] previousPassengers = getPreviousPassengerState(entityID, vehicle, playerData); + playerData.nettyData().consumeUnresolvedPassengers(entityID); // throw away any unresolved passengers as this new packet will have the correct passenger list + // "Stale" passengers are passengers that the previous authoritative state said were mounted, + // but the new full replacement passenger list no longer contains. + clearStalePassengerReferences(entityID, previousPassengers, passengers, playerData); + if (vehicle == null) { + playerData.nettyData().setUnresolvedPassengers(entityID, passengers); return false; } - return handleEntityPassengersNow(entity, passengers, playerData, currentTick); + return handleEntityPassengersNow(vehicle, passengers, playerData); } //This (and leash handling) leaks some info to the client, as it will receive the passenger packet even if the passengers are auto-hidden once parsed, but as the packet doesn't include any location or type info, this shouldn't be too incriminating. - boolean handleEntityPassengersNow(NettyEntityLocatable entity, int[] passengers, PlayerData playerData, int currentTick) { + boolean handleEntityPassengersNow(NettyEntityLocatable entity, int[] passengers, PlayerData playerData) { int entityID = entity.entityID(); entity.setPassengerIDs(passengers); - int blockingEntityID = NO_VEHICLE; + int[] unresolvedPassengers = null; for (int passengerID : passengers) { NettyEntityLocatable passenger = playerData.entityFromID(passengerID); if (passenger == null) { - if (blockingEntityID == NO_VEHICLE) { - blockingEntityID = passengerID; - } + unresolvedPassengers = PrimitiveIntArrayList.add(unresolvedPassengers, passengerID); continue; } passenger.setVehicleID(entityID); } - if (blockingEntityID != NO_VEHICLE) { - queuePassengerRetry(playerData, blockingEntityID, entityID, passengers, currentTick); - } + playerData.nettyData().setUnresolvedPassengers(entityID, unresolvedPassengers); checkVehicle(entity, playerData); if (cancelIfEnabledAndHidden(entityID, playerData)) return true; boolean passengersNotVisible = false; - ArrayList visiblePassengers = new ArrayList<>(passengers.length); + IntArrayList visiblePassengers = new IntArrayList(passengers.length); for (int passengerID : passengers) { - if (cancelIfEnabledAndHidden(passengerID, playerData)) { + NettyEntityLocatable passenger = playerData.entityFromID(passengerID); + if (passenger == null) { + visiblePassengers.add(passengerID); + } + else if (cancelIfEnabledAndHidden(passenger, playerData)) { passengersNotVisible = true; } else { @@ -239,34 +245,50 @@ boolean handleEntityPassengersNow(NettyEntityLocatable entity, int[] passen return passengersNotVisible; } - private void queuePassengerRetry(PlayerData playerData, int queueEntityID, int vehicleEntityID, int[] passengers, int submittedTick) { - playerData.nettyData().addPostEntitySpawnTask(queueEntityID, new PassengerReconciliationTask(playerData, queueEntityID, vehicleEntityID, passengers, submittedTick)); + private int[] getPreviousPassengerState(int vehicleID, NettyEntityLocatable vehicle, PlayerData playerData) { + if (vehicle != null) { + int[] previousPassengerIDs = vehicle.passengerIDs(); + if (!PrimitiveIntArrayList.isEmpty(previousPassengerIDs)) { + return previousPassengerIDs; + } + } + return playerData.nettyData().getUnresolvedPassengers(vehicleID); } - private void checkVehicle(NettyEntityLocatable entity, PlayerData playerData) { - int vehicleID = entity.vehicleID(); - if (vehicleID >= 0) { - NettyEntityLocatable vehicle = playerData.entityFromID(vehicleID); - if (vehicle == null) { - Logger.error(new RuntimeException("Found null vehicle when handling entity passengers packet, vehicleID=" + vehicleID + " for player: " + playerData.getPlayerUUID()), 2, PacketEntityViewController.class); - return; - } - if (cancelIfEnabledAndHidden(vehicleID, playerData)) { - //Vehicle is hidden, so this entity should be hidden as well. No need to check passengers. - return; + /** + * Clears reverse vehicle links for passengers that were part of the previous vehicle state, + * but are absent from the new authoritative replacement list. + */ + private void clearStalePassengerReferences(int vehicleID, int[] previousPassengers, int[] newPassengers, PlayerData playerData) { + if (PrimitiveIntArrayList.isEmpty(previousPassengers)) { + return; + } + for (int previousPassengerID : previousPassengers) { + if (PrimitiveIntArrayList.contains(newPassengers, previousPassengerID)) { + continue; } - //Vehicle is visible, but this entity may not be. - if (cancelIfEnabledAndHidden(entity.entityID(), playerData)) { - return; + NettyEntityLocatable previousPassenger = playerData.entityFromID(previousPassengerID); + if (previousPassenger != null && previousPassenger.vehicleID() == vehicleID) { + previousPassenger.setVehicleID(NO_VEHICLE); } - ArrayList passengers = new ArrayList<>(); - passengers.add(entity.entityID()); - sendEntityPassengerPacket(vehicleID, passengers, playerData); } } + private void checkVehicle(NettyEntityLocatable entity, PlayerData playerData) { + int vehicleID = entity.vehicleID(); + if (vehicleID < 0) { + return; + } + NettyEntityLocatable vehicle = playerData.entityFromID(vehicleID); + if (vehicle == null) { + return; + } + resendPassengerStateIfClientVisible(vehicle, playerData); + } + protected void handleDestroyEntities(int[] entityIDs, PlayerData playerData, int currentTick) { for (int entityID : entityIDs) { + clearPassengerReferencesForDestroyedEntity(entityID, playerData); clearPendingHolderReference(entityID, playerData); playerData.nettyData().removeUnresolvedLeashedEntityFromAll(entityID); playerData.nettyData().clearPendingPostSpawnTasksForEntity(entityID); @@ -280,6 +302,52 @@ protected void handleDestroyEntities(int[] entityIDs, PlayerData playerData, int } } + private void clearPassengerReferencesForDestroyedEntity(int entityID, PlayerData playerData) { + int[] unresolvedPassengers = playerData.nettyData().consumeUnresolvedPassengers(entityID); + if (!PrimitiveIntArrayList.isEmpty(unresolvedPassengers)) { + for (int passengerID : unresolvedPassengers) { + NettyEntityLocatable passenger = playerData.entityFromID(passengerID); + if (passenger != null && passenger.vehicleID() == entityID) { + passenger.setVehicleID(NO_VEHICLE); + } + } + } + + int unresolvedVehicleID = playerData.nettyData().consumeUnresolvedVehicleForPassenger(entityID); + if (unresolvedVehicleID != NO_VEHICLE) { + NettyEntityLocatable unresolvedVehicle = playerData.entityFromID(unresolvedVehicleID); + if (unresolvedVehicle != null) { + // this can occur if the vehicle existed at the time of the passenger packet but not the passenger, and somehow the passenger never got resolved to the vehicle (missing spawn packets etc). In reality this should never happen. + unresolvedVehicle.setPassengerIDs(PrimitiveIntArrayList.remove(unresolvedVehicle.passengerIDs(), entityID)); + } + } + + NettyEntityLocatable entity = playerData.entityFromID(entityID); + if (entity == null) { + return; + } + + int[] currentPassengerIDs = entity.passengerIDs(); + if (!PrimitiveIntArrayList.isEmpty(currentPassengerIDs)) { + for (int passengerID : currentPassengerIDs) { + NettyEntityLocatable passenger = playerData.entityFromID(passengerID); + if (passenger != null && passenger.vehicleID() == entityID) { + passenger.setVehicleID(NO_VEHICLE); + } + } + } + + int vehicleID = entity.vehicleID(); + if (vehicleID == NO_VEHICLE) { + return; + } + NettyEntityLocatable vehicle = playerData.entityFromID(vehicleID); + if (vehicle != null) { + vehicle.setPassengerIDs(PrimitiveIntArrayList.remove(vehicle.passengerIDs(), entityID)); + } + entity.setVehicleID(NO_VEHICLE); + } + private void clearPendingHolderReference(int holderEntityID, PlayerData playerData) { int[] pendingLeashedEntityIDs = playerData.nettyData().consumeUnresolvedLeashes(holderEntityID); if (PrimitiveIntArrayList.isEmpty(pendingLeashedEntityIDs)) { @@ -392,6 +460,54 @@ protected boolean cancelIfEnabledAndHidden(NettyEntityLocatable entity, Pla return getCorrectConfig(playerData.viewFromEntityID(entity.entityID())).enabled(); // If this statement is reached, the entity should be hidden, so if the config is enabled it is hidden. } + /** + * Replays any passenger relationship that was blocked earlier because either: + * 1. this entity is the vehicle and one or more passengers were missing, or + * 2. this entity is the passenger and the vehicle was already known. + */ + protected void reconcileUnresolvedPassengers(NettyEntityLocatable insertedEntity, PlayerData playerData) { + int[] pendingPassengers = playerData.nettyData().getUnresolvedPassengers(insertedEntity.entityID()); + if (!PrimitiveIntArrayList.isEmpty(pendingPassengers)) { + playerData.nettyData().consumeUnresolvedPassengers(insertedEntity.entityID()); + handleEntityPassengersNow(insertedEntity, pendingPassengers, playerData); + resendPassengerStateIfClientVisible(insertedEntity, playerData); + } + + int unresolvedVehicleID = playerData.nettyData().getUnresolvedVehicleForPassenger(insertedEntity.entityID()); + if (unresolvedVehicleID == NO_VEHICLE) { + return; + } + NettyEntityLocatable vehicle = playerData.entityFromID(unresolvedVehicleID); + if (vehicle == null) { + return; + } + insertedEntity.setVehicleID(unresolvedVehicleID); + playerData.nettyData().removeUnresolvedPassengerLink(insertedEntity.entityID(), unresolvedVehicleID); + resendPassengerStateIfClientVisible(vehicle, playerData); + } + + protected void resendPassengerStateIfClientVisible(NettyEntityLocatable vehicle, PlayerData playerData) { + if (!vehicle.clientVisible()) { + return; + } + sendEntityPassengerPacket(vehicle.entityID(), collectVisiblePassengers(vehicle.passengerIDs(), playerData), playerData); + } + + private IntArrayList collectVisiblePassengers(int[] passengerIDs, PlayerData playerData) { + int size = passengerIDs == null ? 0 : passengerIDs.length; + IntArrayList visiblePassengers = new IntArrayList(size); + if (passengerIDs == null) { + return visiblePassengers; + } + for (int passengerID : passengerIDs) { + NettyEntityLocatable passenger = playerData.entityFromID(passengerID); + if (passenger != null && passenger.visible()) { + visiblePassengers.add(passengerID); + } + } + return visiblePassengers; + } + /** * @return The created entity, with a default visibility of true. Does not insert the entity into any views, that is the responsibility of the caller. */ @@ -420,7 +536,7 @@ protected boolean cancelIfEnabledAndHidden(NettyEntityLocatable entity, Pla protected abstract int processEntityVelocityPacket(P packet, PlayerData playerData, int currentTick); /**Silently sends the provided array of entities as passengers for the required vehicle.*/ - protected abstract void sendEntityPassengerPacket(int vehicle, ArrayList passengers, PlayerData playerData); + protected abstract void sendEntityPassengerPacket(int vehicle, IntArrayList passengers, PlayerData playerData); protected abstract void insertEntityToPlayerView(NettyEntityLocatable entity, PlayerData playerData); diff --git a/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PassengerReconciliationTask.java b/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PassengerReconciliationTask.java deleted file mode 100644 index 67fcc591..00000000 --- a/core/src/main/java/games/cubi/raycastedantiesp/core/view/controller/PassengerReconciliationTask.java +++ /dev/null @@ -1,44 +0,0 @@ -package games.cubi.raycastedantiesp.core.view.controller; - -import games.cubi.logs.Logger; -import games.cubi.raycastedantiesp.core.locatables.NettyEntityLocatable; -import games.cubi.raycastedantiesp.core.players.PlayerData; -import games.cubi.raycastedantiesp.core.utils.BaseEntitySpawnTask; - -import java.util.Arrays; - -final class PassengerReconciliationTask extends BaseEntitySpawnTask { - private final PlayerData playerData; - private final int queuedEntityId; - private final int vehicleEntityId; - private final int[] passengerIds; - - PassengerReconciliationTask(PlayerData playerData, int queuedEntityId, int vehicleEntityId, int[] passengerIds, int submittedTick) { - super(submittedTick); - this.playerData = playerData; - this.queuedEntityId = queuedEntityId; - this.vehicleEntityId = vehicleEntityId; - this.passengerIds = passengerIds.clone(); - } - - @Override - public void run() { - NettyEntityLocatable vehicle = playerData.entityFromID(vehicleEntityId); - if (vehicle == null) { - Logger.error("Reconciliation fail: Attempted to reconcile passengers for unknown vehicle, queuedEntityId=" + queuedEntityId + " vehicleEntityId=" + vehicleEntityId, 3, this.getClass()); - return; - } - PacketEntityViewController.get().handleEntityPassengersNow(vehicle, passengerIds, playerData, submittedTick); - } - - @Override - public String toString() { - return "PassengerReconciliationTask{" + - "submittedTick=" + submittedTick + - ", queuedEntityId=" + queuedEntityId + - ", vehicleEntityId=" + vehicleEntityId + - ", passengerIds=" + Arrays.toString(passengerIds) + - ", playerUUID=" + playerData.getPlayerUUID() + - '}'; - } -} diff --git a/packetevents/build.gradle.kts b/packetevents/build.gradle.kts index ce554edb..58cee0da 100644 --- a/packetevents/build.gradle.kts +++ b/packetevents/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { compileOnly("com.github.retrooper:packetevents-api:2.12.0") compileOnly("org.spongepowered:configurate-core:4.2.0") + compileOnly("it.unimi.dsi:fastutil:8.5.18") } java { diff --git a/packetevents/src/main/java/games/cubi/raycastedantiesp/packetevents/viewcontrollers/PacketEventsEntityViewController.java b/packetevents/src/main/java/games/cubi/raycastedantiesp/packetevents/viewcontrollers/PacketEventsEntityViewController.java index 39bb989c..0b00cb17 100644 --- a/packetevents/src/main/java/games/cubi/raycastedantiesp/packetevents/viewcontrollers/PacketEventsEntityViewController.java +++ b/packetevents/src/main/java/games/cubi/raycastedantiesp/packetevents/viewcontrollers/PacketEventsEntityViewController.java @@ -25,6 +25,7 @@ import games.cubi.raycastedantiesp.core.view.controller.PacketEntityViewController; import games.cubi.raycastedantiesp.packetevents.locatables.PacketEventsEntity; import games.cubi.raycastedantiesp.packetevents.replaydata.PacketEventsEntityReplayData; +import it.unimi.dsi.fastutil.ints.IntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -371,13 +372,13 @@ protected int processEntityVelocityPacket(PacketWrapper packet, PlayerData pl } @Override - protected void sendEntityPassengerPacket(int vehicle, ArrayList passengers, PlayerData playerData) { + protected void sendEntityPassengerPacket(int vehicle, IntArrayList passengers, PlayerData playerData) { NettyEntityLocatable entity = playerData.entityFromID(vehicle); if (entity == null) { Logger.error("Attempted to send passenger packet for unknown entity, id=" + vehicle, 2, PacketEventsEntityViewController.class); return; } - WrapperPlayServerSetPassengers packet = new WrapperPlayServerSetPassengers(vehicle, passengers.stream().mapToInt(Integer::intValue).toArray()); + WrapperPlayServerSetPassengers packet = new WrapperPlayServerSetPassengers(vehicle, passengers.toIntArray()); Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(playerData.getPlayerUUID()); PacketEvents.getAPI().getProtocolManager().getUser(channel).writePacketSilently(packet); } @@ -624,11 +625,15 @@ private void sendEntityAbsoluteCorrection(User viewer, PacketEventsEntity entity protected void insertEntityToPlayerView(NettyEntityLocatable entity, PlayerData playerData) { playerData.playerView().insertEntity(entity.cast()); + // Passenger relationships can arrive before spawn/pairing completes, so resolve them as soon as the entity becomes known. + reconcileUnresolvedPassengers(entity, playerData); reconcileUnresolvedLeashes(entity, playerData); } protected void insertEntityToEntityView(NettyEntityLocatable entity, PlayerData playerData) { playerData.entityView().insertEntity(entity.cast()); //todo: no need to put here, move to abstract packet view controller + // Passenger relationships can arrive before spawn/pairing completes, so resolve them as soon as the entity becomes known. + reconcileUnresolvedPassengers(entity, playerData); reconcileUnresolvedLeashes(entity, playerData); } diff --git a/platform-paper/build.gradle.kts b/platform-paper/build.gradle.kts index abd6e055..0143bf33 100644 --- a/platform-paper/build.gradle.kts +++ b/platform-paper/build.gradle.kts @@ -41,7 +41,7 @@ val javaToolchainService = project.extensions.getByType(JavaToolchainService::cl group = "games.cubi.raycastedantiesp.paper" -val platformPaperVersion: String = "0.4.6-SNAPSHOT" +val platformPaperVersion: String = "0.4.10-SNAPSHOT" val coreVersion = project(":core").version.toString() val commitShort = providers.exec { diff --git a/platform-paper/src/main/java/games/cubi/raycastedantiesp/paper/PaperLoggerAdapter.java b/platform-paper/src/main/java/games/cubi/raycastedantiesp/paper/PaperLoggerAdapter.java index 97c2351f..9c1c6fc5 100644 --- a/platform-paper/src/main/java/games/cubi/raycastedantiesp/paper/PaperLoggerAdapter.java +++ b/platform-paper/src/main/java/games/cubi/raycastedantiesp/paper/PaperLoggerAdapter.java @@ -115,7 +115,7 @@ public void warning(Throwable throwable, @Range(from = 1, to = 10) int level, Cl @Deprecated @Override public void debug(String message) { - forwardLog(message, Level.INFO, 1); + forwardLog(message, Level.INFO, 10); } @Override