2026.01.27-734d39026
This commit is contained in:
@@ -2,7 +2,9 @@ package com.hypixel.hytale.builtin.adventure.teleporter;
|
|||||||
|
|
||||||
import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter;
|
import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter;
|
||||||
import com.hypixel.hytale.builtin.adventure.teleporter.interaction.server.TeleporterInteraction;
|
import com.hypixel.hytale.builtin.adventure.teleporter.interaction.server.TeleporterInteraction;
|
||||||
|
import com.hypixel.hytale.builtin.adventure.teleporter.interaction.server.UsedTeleporter;
|
||||||
import com.hypixel.hytale.builtin.adventure.teleporter.page.TeleporterSettingsPageSupplier;
|
import com.hypixel.hytale.builtin.adventure.teleporter.page.TeleporterSettingsPageSupplier;
|
||||||
|
import com.hypixel.hytale.builtin.adventure.teleporter.system.ClearUsedTeleporterSystem;
|
||||||
import com.hypixel.hytale.builtin.adventure.teleporter.system.CreateWarpWhenTeleporterPlacedSystem;
|
import com.hypixel.hytale.builtin.adventure.teleporter.system.CreateWarpWhenTeleporterPlacedSystem;
|
||||||
import com.hypixel.hytale.builtin.teleport.TeleportPlugin;
|
import com.hypixel.hytale.builtin.teleport.TeleportPlugin;
|
||||||
import com.hypixel.hytale.component.AddReason;
|
import com.hypixel.hytale.component.AddReason;
|
||||||
@@ -19,12 +21,14 @@ import com.hypixel.hytale.server.core.modules.interaction.interaction.config.ser
|
|||||||
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
|
||||||
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
||||||
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
|
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class TeleporterPlugin extends JavaPlugin {
|
public class TeleporterPlugin extends JavaPlugin {
|
||||||
private static TeleporterPlugin instance;
|
private static TeleporterPlugin instance;
|
||||||
private ComponentType<ChunkStore, Teleporter> teleporterComponentType;
|
private ComponentType<ChunkStore, Teleporter> teleporterComponentType;
|
||||||
|
private ComponentType<EntityStore, UsedTeleporter> usedTeleporterComponentType;
|
||||||
|
|
||||||
public static TeleporterPlugin get() {
|
public static TeleporterPlugin get() {
|
||||||
return instance;
|
return instance;
|
||||||
@@ -41,6 +45,8 @@ public class TeleporterPlugin extends JavaPlugin {
|
|||||||
this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefChangeSystem());
|
this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefChangeSystem());
|
||||||
this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefSystem());
|
this.getChunkStoreRegistry().registerSystem(new TeleporterPlugin.TeleporterOwnedWarpRefSystem());
|
||||||
this.getChunkStoreRegistry().registerSystem(new CreateWarpWhenTeleporterPlacedSystem());
|
this.getChunkStoreRegistry().registerSystem(new CreateWarpWhenTeleporterPlacedSystem());
|
||||||
|
this.usedTeleporterComponentType = this.getEntityStoreRegistry().registerComponent(UsedTeleporter.class, UsedTeleporter::new);
|
||||||
|
this.getEntityStoreRegistry().registerSystem(new ClearUsedTeleporterSystem());
|
||||||
this.getCodecRegistry(Interaction.CODEC).register("Teleporter", TeleporterInteraction.class, TeleporterInteraction.CODEC);
|
this.getCodecRegistry(Interaction.CODEC).register("Teleporter", TeleporterInteraction.class, TeleporterInteraction.CODEC);
|
||||||
this.getCodecRegistry(OpenCustomUIInteraction.PAGE_CODEC)
|
this.getCodecRegistry(OpenCustomUIInteraction.PAGE_CODEC)
|
||||||
.register("Teleporter", TeleporterSettingsPageSupplier.class, TeleporterSettingsPageSupplier.CODEC);
|
.register("Teleporter", TeleporterSettingsPageSupplier.class, TeleporterSettingsPageSupplier.CODEC);
|
||||||
@@ -50,6 +56,10 @@ public class TeleporterPlugin extends JavaPlugin {
|
|||||||
return this.teleporterComponentType;
|
return this.teleporterComponentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentType<EntityStore, UsedTeleporter> getUsedTeleporterComponentType() {
|
||||||
|
return this.usedTeleporterComponentType;
|
||||||
|
}
|
||||||
|
|
||||||
private static class TeleporterOwnedWarpRefChangeSystem extends RefChangeSystem<ChunkStore, Teleporter> {
|
private static class TeleporterOwnedWarpRefChangeSystem extends RefChangeSystem<ChunkStore, Teleporter> {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.hypixel.hytale.builtin.adventure.teleporter.component.Teleporter;
|
|||||||
import com.hypixel.hytale.codec.Codec;
|
import com.hypixel.hytale.codec.Codec;
|
||||||
import com.hypixel.hytale.codec.KeyedCodec;
|
import com.hypixel.hytale.codec.KeyedCodec;
|
||||||
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
|
import com.hypixel.hytale.codec.validation.Validators;
|
||||||
import com.hypixel.hytale.component.Archetype;
|
import com.hypixel.hytale.component.Archetype;
|
||||||
import com.hypixel.hytale.component.CommandBuffer;
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
import com.hypixel.hytale.component.Ref;
|
import com.hypixel.hytale.component.Ref;
|
||||||
@@ -49,10 +50,30 @@ public class TeleporterInteraction extends SimpleBlockInteraction {
|
|||||||
)
|
)
|
||||||
.documentation("The particle to play on the entity when teleporting.")
|
.documentation("The particle to play on the entity when teleporting.")
|
||||||
.add()
|
.add()
|
||||||
|
.<Double>appendInherited(
|
||||||
|
new KeyedCodec<>("ClearOutXZ", Codec.DOUBLE),
|
||||||
|
(interaction, s) -> interaction.clearoutXZ = s,
|
||||||
|
interaction -> interaction.clearoutXZ,
|
||||||
|
(interaction, parent) -> interaction.clearoutXZ = parent.clearoutXZ
|
||||||
|
)
|
||||||
|
.addValidator(Validators.greaterThanOrEqual(0.0))
|
||||||
|
.documentation("Upon reaching the warp destination, how far away one has to move on the XZ plane in order to use another Teleporter.")
|
||||||
|
.add()
|
||||||
|
.<Double>appendInherited(
|
||||||
|
new KeyedCodec<>("ClearOutY", Codec.DOUBLE),
|
||||||
|
(interaction, s) -> interaction.clearoutY = s,
|
||||||
|
interaction -> interaction.clearoutY,
|
||||||
|
(interaction, parent) -> interaction.clearoutY = parent.clearoutY
|
||||||
|
)
|
||||||
|
.addValidator(Validators.greaterThanOrEqual(0.0))
|
||||||
|
.documentation("Upon reaching the warp destination, how far away one has to move along the Y axis in order to use another Teleporter.")
|
||||||
|
.add()
|
||||||
.build();
|
.build();
|
||||||
private static final Duration TELEPORT_GLOBAL_COOLDOWN = Duration.ofMillis(250L);
|
private static final Duration TELEPORTER_GLOBAL_COOLDOWN = Duration.ofMillis(100L);
|
||||||
@Nullable
|
@Nullable
|
||||||
private String particle;
|
private String particle;
|
||||||
|
private double clearoutXZ = 1.3;
|
||||||
|
private double clearoutY = 2.5;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
@@ -88,30 +109,37 @@ public class TeleporterInteraction extends SimpleBlockInteraction {
|
|||||||
if (playerComponent == null || !playerComponent.isWaitingForClientReady()) {
|
if (playerComponent == null || !playerComponent.isWaitingForClientReady()) {
|
||||||
Archetype<EntityStore> archetype = commandBuffer.getArchetype(ref);
|
Archetype<EntityStore> archetype = commandBuffer.getArchetype(ref);
|
||||||
if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) {
|
if (!archetype.contains(Teleport.getComponentType()) && !archetype.contains(PendingTeleport.getComponentType())) {
|
||||||
|
if (!archetype.contains(UsedTeleporter.getComponentType())) {
|
||||||
WorldChunk worldChunkComponent = chunkRef.getStore().getComponent(chunkRef, WorldChunk.getComponentType());
|
WorldChunk worldChunkComponent = chunkRef.getStore().getComponent(chunkRef, WorldChunk.getComponentType());
|
||||||
|
if (worldChunkComponent != null) {
|
||||||
assert worldChunkComponent != null;
|
|
||||||
|
|
||||||
BlockType blockType = worldChunkComponent.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z);
|
BlockType blockType = worldChunkComponent.getBlockType(targetBlock.x, targetBlock.y, targetBlock.z);
|
||||||
|
if (blockType != null) {
|
||||||
if (!teleporter.isValid()) {
|
if (!teleporter.isValid()) {
|
||||||
String currentState = blockType.getStateForBlock(blockType);
|
String currentState = blockType.getStateForBlock(blockType);
|
||||||
if (!"default".equals(currentState)) {
|
if (!"default".equals(currentState)) {
|
||||||
BlockType variantBlockType = blockType.getBlockForState("default");
|
BlockType variantBlockType = blockType.getBlockForState("default");
|
||||||
if (variantBlockType != null) {
|
if (variantBlockType != null) {
|
||||||
worldChunkComponent.setBlockInteractionState(targetBlock.x, targetBlock.y, targetBlock.z, variantBlockType, "default", true);
|
worldChunkComponent.setBlockInteractionState(
|
||||||
|
targetBlock.x, targetBlock.y, targetBlock.z, variantBlockType, "default", true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType());
|
TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType());
|
||||||
|
if (transformComponent != null) {
|
||||||
assert transformComponent != null;
|
Teleport teleportComponent = teleporter.toTeleport(
|
||||||
|
transformComponent.getPosition(), transformComponent.getRotation(), targetBlock
|
||||||
Teleport teleportComponent = teleporter.toTeleport(transformComponent.getPosition(), transformComponent.getRotation(), targetBlock);
|
);
|
||||||
if (teleportComponent != null) {
|
if (teleportComponent != null) {
|
||||||
TeleportRecord recorder = commandBuffer.getComponent(ref, TeleportRecord.getComponentType());
|
TeleportRecord recorder = commandBuffer.getComponent(ref, TeleportRecord.getComponentType());
|
||||||
if (recorder == null || recorder.hasElapsedSinceLastTeleport(TELEPORT_GLOBAL_COOLDOWN)) {
|
if (recorder == null || recorder.hasElapsedSinceLastTeleport(TELEPORTER_GLOBAL_COOLDOWN)) {
|
||||||
commandBuffer.addComponent(ref, Teleport.getComponentType(), teleportComponent);
|
commandBuffer.addComponent(ref, Teleport.getComponentType(), teleportComponent);
|
||||||
|
commandBuffer.addComponent(
|
||||||
|
ref,
|
||||||
|
UsedTeleporter.getComponentType(),
|
||||||
|
new UsedTeleporter(teleporter.getWorldUuid(), teleportComponent.getPosition(), this.clearoutXZ, this.clearoutY)
|
||||||
|
);
|
||||||
if (this.particle != null) {
|
if (this.particle != null) {
|
||||||
Vector3d particlePosition = transformComponent.getPosition();
|
Vector3d particlePosition = transformComponent.getPosition();
|
||||||
SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = commandBuffer.getResource(
|
SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = commandBuffer.getResource(
|
||||||
@@ -131,6 +159,10 @@ public class TeleporterInteraction extends SimpleBlockInteraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void simulateInteractWithBlock(
|
protected void simulateInteractWithBlock(
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.hypixel.hytale.builtin.adventure.teleporter.interaction.server;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.builtin.adventure.teleporter.TeleporterPlugin;
|
||||||
|
import com.hypixel.hytale.component.Component;
|
||||||
|
import com.hypixel.hytale.component.ComponentType;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||||
|
|
||||||
|
public class UsedTeleporter implements Component<EntityStore> {
|
||||||
|
@Nullable
|
||||||
|
private UUID destinationWorldUuid;
|
||||||
|
private Vector3d destinationPosition;
|
||||||
|
private double clearOutXZ;
|
||||||
|
private double clearOutXZSquared;
|
||||||
|
private double clearOutY;
|
||||||
|
|
||||||
|
public static ComponentType<EntityStore, UsedTeleporter> getComponentType() {
|
||||||
|
return TeleporterPlugin.get().getUsedTeleporterComponentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsedTeleporter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsedTeleporter(@Nullable UUID destinationWorldUuid, Vector3d destinationPosition, double clearOutXZ, double clearOutY) {
|
||||||
|
this.destinationWorldUuid = destinationWorldUuid;
|
||||||
|
this.destinationPosition = destinationPosition;
|
||||||
|
this.clearOutXZ = clearOutXZ;
|
||||||
|
this.clearOutXZSquared = clearOutXZ * clearOutXZ;
|
||||||
|
this.clearOutY = clearOutY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public UUID getDestinationWorldUuid() {
|
||||||
|
return this.destinationWorldUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3d getDestinationPosition() {
|
||||||
|
return this.destinationPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getClearOutXZ() {
|
||||||
|
return this.clearOutXZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getClearOutXZSquared() {
|
||||||
|
return this.clearOutXZSquared;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getClearOutY() {
|
||||||
|
return this.clearOutY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NullableDecl
|
||||||
|
@Override
|
||||||
|
public Component<EntityStore> clone() {
|
||||||
|
UsedTeleporter clone = new UsedTeleporter();
|
||||||
|
clone.destinationWorldUuid = this.destinationWorldUuid;
|
||||||
|
clone.destinationPosition = this.destinationPosition.clone();
|
||||||
|
clone.clearOutXZ = this.clearOutXZ;
|
||||||
|
clone.clearOutXZSquared = this.clearOutXZSquared;
|
||||||
|
clone.clearOutY = this.clearOutY;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.hypixel.hytale.builtin.adventure.teleporter.system;
|
||||||
|
|
||||||
|
import com.hypixel.hytale.builtin.adventure.teleporter.interaction.server.UsedTeleporter;
|
||||||
|
import com.hypixel.hytale.component.ArchetypeChunk;
|
||||||
|
import com.hypixel.hytale.component.CommandBuffer;
|
||||||
|
import com.hypixel.hytale.component.Ref;
|
||||||
|
import com.hypixel.hytale.component.Store;
|
||||||
|
import com.hypixel.hytale.component.query.Query;
|
||||||
|
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector2d;
|
||||||
|
import com.hypixel.hytale.math.vector.Vector3d;
|
||||||
|
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.World;
|
||||||
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NonNullDecl;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||||
|
|
||||||
|
public class ClearUsedTeleporterSystem extends EntityTickingSystem<EntityStore> {
|
||||||
|
@Override
|
||||||
|
public void tick(
|
||||||
|
float dt,
|
||||||
|
int index,
|
||||||
|
@NonNullDecl ArchetypeChunk<EntityStore> archetypeChunk,
|
||||||
|
@NonNullDecl Store<EntityStore> store,
|
||||||
|
@NonNullDecl CommandBuffer<EntityStore> commandBuffer
|
||||||
|
) {
|
||||||
|
World world = store.getExternalData().getWorld();
|
||||||
|
UsedTeleporter usedTeleporter = archetypeChunk.getComponent(index, UsedTeleporter.getComponentType());
|
||||||
|
TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
|
||||||
|
if (shouldClear(world, usedTeleporter, transformComponent)) {
|
||||||
|
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
|
||||||
|
commandBuffer.removeComponent(ref, UsedTeleporter.getComponentType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldClear(World entityWorld, UsedTeleporter usedTeleporter, @Nullable TransformComponent transformComponent) {
|
||||||
|
if (transformComponent == null) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
UUID destinationWorldUuid = usedTeleporter.getDestinationWorldUuid();
|
||||||
|
if (destinationWorldUuid != null && !entityWorld.getWorldConfig().getUuid().equals(destinationWorldUuid)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Vector3d entityPosition = transformComponent.getPosition();
|
||||||
|
Vector3d destinationPosition = usedTeleporter.getDestinationPosition();
|
||||||
|
double deltaY = Math.abs(entityPosition.y - destinationPosition.y);
|
||||||
|
double distanceXZsq = Vector2d.distanceSquared(entityPosition.x, entityPosition.z, destinationPosition.x, destinationPosition.z);
|
||||||
|
return deltaY > usedTeleporter.getClearOutY() || distanceXZsq > usedTeleporter.getClearOutXZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NullableDecl
|
||||||
|
@Override
|
||||||
|
public Query<EntityStore> getQuery() {
|
||||||
|
return UsedTeleporter.getComponentType();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.hypixel.hytale.server.core.auth;
|
package com.hypixel.hytale.server.core.auth;
|
||||||
|
|
||||||
import com.hypixel.hytale.common.util.java.ManifestUtil;
|
import com.hypixel.hytale.common.util.java.ManifestUtil;
|
||||||
|
import java.time.Duration;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class AuthConfig {
|
public class AuthConfig {
|
||||||
@@ -17,7 +18,7 @@ public class AuthConfig {
|
|||||||
public static final String SCOPE_CLIENT = "hytale:client";
|
public static final String SCOPE_CLIENT = "hytale:client";
|
||||||
public static final String SCOPE_SERVER = "hytale:server";
|
public static final String SCOPE_SERVER = "hytale:server";
|
||||||
public static final String SCOPE_EDITOR = "hytale:editor";
|
public static final String SCOPE_EDITOR = "hytale:editor";
|
||||||
public static final int HTTP_TIMEOUT_SECONDS = 10;
|
public static final Duration HTTP_TIMEOUT = Duration.ofSeconds(30L);
|
||||||
public static final int DEVICE_POLL_INTERVAL_SECONDS = 15;
|
public static final int DEVICE_POLL_INTERVAL_SECONDS = 15;
|
||||||
public static final String ENV_SERVER_AUDIENCE = "HYTALE_SERVER_AUDIENCE";
|
public static final String ENV_SERVER_AUDIENCE = "HYTALE_SERVER_AUDIENCE";
|
||||||
public static final String ENV_SERVER_IDENTITY_TOKEN = "HYTALE_SERVER_IDENTITY_TOKEN";
|
public static final String ENV_SERVER_IDENTITY_TOKEN = "HYTALE_SERVER_IDENTITY_TOKEN";
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import com.nimbusds.jwt.JWTClaimsSet;
|
|||||||
import com.nimbusds.jwt.SignedJWT;
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@@ -28,14 +28,14 @@ public class JWTValidator {
|
|||||||
private static final JWSAlgorithm SUPPORTED_ALGORITHM = JWSAlgorithm.EdDSA;
|
private static final JWSAlgorithm SUPPORTED_ALGORITHM = JWSAlgorithm.EdDSA;
|
||||||
private static final int MIN_SIGNATURE_LENGTH = 80;
|
private static final int MIN_SIGNATURE_LENGTH = 80;
|
||||||
private static final int MAX_SIGNATURE_LENGTH = 90;
|
private static final int MAX_SIGNATURE_LENGTH = 90;
|
||||||
|
private static final Duration JWKS_REFRESH_MIN_INTERVAL = Duration.ofMinutes(5L);
|
||||||
private final SessionServiceClient sessionServiceClient;
|
private final SessionServiceClient sessionServiceClient;
|
||||||
private final String expectedIssuer;
|
private final String expectedIssuer;
|
||||||
private final String expectedAudience;
|
private final String expectedAudience;
|
||||||
private volatile JWKSet cachedJwkSet;
|
private volatile JWKSet cachedJwkSet;
|
||||||
private volatile long jwksCacheExpiry;
|
|
||||||
private final long jwksCacheDurationMs = TimeUnit.HOURS.toMillis(1L);
|
|
||||||
private final ReentrantLock jwksFetchLock = new ReentrantLock();
|
private final ReentrantLock jwksFetchLock = new ReentrantLock();
|
||||||
private volatile CompletableFuture<JWKSet> pendingFetch = null;
|
private volatile CompletableFuture<JWKSet> pendingFetch = null;
|
||||||
|
private volatile Instant lastJwksRefresh;
|
||||||
|
|
||||||
public JWTValidator(@Nonnull SessionServiceClient sessionServiceClient, @Nonnull String expectedIssuer, @Nonnull String expectedAudience) {
|
public JWTValidator(@Nonnull SessionServiceClient sessionServiceClient, @Nonnull String expectedIssuer, @Nonnull String expectedAudience) {
|
||||||
this.sessionServiceClient = sessionServiceClient;
|
this.sessionServiceClient = sessionServiceClient;
|
||||||
@@ -171,15 +171,14 @@ public class JWTValidator {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JWKSet getJwkSet(boolean forceRefresh) {
|
private JWKSet getJwkSet(boolean forceRefresh) {
|
||||||
long now = System.currentTimeMillis();
|
if (!forceRefresh && this.cachedJwkSet != null) {
|
||||||
if (!forceRefresh && this.cachedJwkSet != null && now < this.jwksCacheExpiry) {
|
|
||||||
return this.cachedJwkSet;
|
return this.cachedJwkSet;
|
||||||
} else {
|
} else {
|
||||||
this.jwksFetchLock.lock();
|
this.jwksFetchLock.lock();
|
||||||
|
|
||||||
JWKSet var5;
|
JWKSet var3;
|
||||||
try {
|
try {
|
||||||
if (!forceRefresh && this.cachedJwkSet != null && now < this.jwksCacheExpiry) {
|
if (!forceRefresh && this.cachedJwkSet != null) {
|
||||||
return this.cachedJwkSet;
|
return this.cachedJwkSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +195,7 @@ public class JWTValidator {
|
|||||||
this.jwksFetchLock.unlock();
|
this.jwksFetchLock.unlock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var5 = existing.join();
|
var3 = existing.join();
|
||||||
} finally {
|
} finally {
|
||||||
this.jwksFetchLock.lock();
|
this.jwksFetchLock.lock();
|
||||||
}
|
}
|
||||||
@@ -204,7 +203,7 @@ public class JWTValidator {
|
|||||||
this.jwksFetchLock.unlock();
|
this.jwksFetchLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
return var5;
|
return var3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +227,8 @@ public class JWTValidator {
|
|||||||
} else {
|
} else {
|
||||||
JWKSet newSet = new JWKSet(jwkList);
|
JWKSet newSet = new JWKSet(jwkList);
|
||||||
this.cachedJwkSet = newSet;
|
this.cachedJwkSet = newSet;
|
||||||
this.jwksCacheExpiry = System.currentTimeMillis() + this.jwksCacheDurationMs;
|
this.lastJwksRefresh = Instant.now();
|
||||||
LOGGER.at(Level.INFO).log("JWKS loaded with %d keys", jwkList.size());
|
LOGGER.at(Level.INFO).log("JWKS loaded with %d keys (cached permanently)", jwkList.size());
|
||||||
return newSet;
|
return newSet;
|
||||||
}
|
}
|
||||||
} catch (Exception var8) {
|
} catch (Exception var8) {
|
||||||
@@ -248,6 +247,9 @@ public class JWTValidator {
|
|||||||
return false;
|
return false;
|
||||||
} else if (this.verifySignature(signedJWT, jwkSet)) {
|
} else if (this.verifySignature(signedJWT, jwkSet)) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (!this.canForceRefreshJwks()) {
|
||||||
|
LOGGER.at(Level.FINE).log("Signature verification failed but JWKS was refreshed recently; skipping refresh");
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
LOGGER.at(Level.INFO).log("Signature verification failed with cached JWKS, retrying with fresh keys");
|
LOGGER.at(Level.INFO).log("Signature verification failed with cached JWKS, retrying with fresh keys");
|
||||||
JWKSet freshJwkSet = this.getJwkSet(true);
|
JWKSet freshJwkSet = this.getJwkSet(true);
|
||||||
@@ -255,6 +257,11 @@ public class JWTValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean canForceRefreshJwks() {
|
||||||
|
Instant lastRefresh = this.lastJwksRefresh;
|
||||||
|
return lastRefresh == null ? true : Duration.between(lastRefresh, Instant.now()).compareTo(JWKS_REFRESH_MIN_INTERVAL) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private JWK convertToJWK(SessionServiceClient.JwkKey key) {
|
private JWK convertToJWK(SessionServiceClient.JwkKey key) {
|
||||||
if (!"OKP".equals(key.kty)) {
|
if (!"OKP".equals(key.kty)) {
|
||||||
@@ -276,7 +283,6 @@ public class JWTValidator {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.cachedJwkSet = null;
|
this.cachedJwkSet = null;
|
||||||
this.jwksCacheExpiry = 0L;
|
|
||||||
this.pendingFetch = null;
|
this.pendingFetch = null;
|
||||||
} finally {
|
} finally {
|
||||||
this.jwksFetchLock.unlock();
|
this.jwksFetchLock.unlock();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.hypixel.hytale.codec.KeyedCodec;
|
|||||||
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
import com.hypixel.hytale.codec.builder.BuilderCodec;
|
||||||
import com.hypixel.hytale.codec.util.RawJsonReader;
|
import com.hypixel.hytale.codec.util.RawJsonReader;
|
||||||
import com.hypixel.hytale.logger.HytaleLogger;
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
@@ -14,7 +15,6 @@ import java.net.http.HttpRequest;
|
|||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -23,14 +23,13 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
public class ProfileServiceClient {
|
public class ProfileServiceClient {
|
||||||
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5L);
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private final String profileServiceUrl;
|
private final String profileServiceUrl;
|
||||||
|
|
||||||
public ProfileServiceClient(@Nonnull String profileServiceUrl) {
|
public ProfileServiceClient(@Nonnull String profileServiceUrl) {
|
||||||
if (profileServiceUrl != null && !profileServiceUrl.isEmpty()) {
|
if (profileServiceUrl != null && !profileServiceUrl.isEmpty()) {
|
||||||
this.profileServiceUrl = profileServiceUrl.endsWith("/") ? profileServiceUrl.substring(0, profileServiceUrl.length() - 1) : profileServiceUrl;
|
this.profileServiceUrl = profileServiceUrl.endsWith("/") ? profileServiceUrl.substring(0, profileServiceUrl.length() - 1) : profileServiceUrl;
|
||||||
this.httpClient = HttpClient.newBuilder().connectTimeout(REQUEST_TIMEOUT).build();
|
this.httpClient = ServiceHttpClientFactory.create(AuthConfig.HTTP_TIMEOUT);
|
||||||
LOGGER.at(Level.INFO).log("Profile Service client initialized for: %s", this.profileServiceUrl);
|
LOGGER.at(Level.INFO).log("Profile Service client initialized for: %s", this.profileServiceUrl);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Profile Service URL cannot be null or empty");
|
throw new IllegalArgumentException("Profile Service URL cannot be null or empty");
|
||||||
@@ -45,7 +44,7 @@ public class ProfileServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + bearerToken)
|
.header("Authorization", "Bearer " + bearerToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.FINE).log("Fetching profile by UUID: %s", uuid);
|
LOGGER.at(Level.FINE).log("Fetching profile by UUID: %s", uuid);
|
||||||
@@ -90,7 +89,7 @@ public class ProfileServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + bearerToken)
|
.header("Authorization", "Bearer " + bearerToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.FINE).log("Fetching profile by username: %s", username);
|
LOGGER.at(Level.FINE).log("Fetching profile by username: %s", username);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.hypixel.hytale.codec.builder.BuilderCodec;
|
|||||||
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
|
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
|
||||||
import com.hypixel.hytale.codec.util.RawJsonReader;
|
import com.hypixel.hytale.codec.util.RawJsonReader;
|
||||||
import com.hypixel.hytale.logger.HytaleLogger;
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
|
import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
@@ -14,24 +15,25 @@ import java.net.http.HttpRequest;
|
|||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.net.http.HttpRequest.BodyPublishers;
|
import java.net.http.HttpRequest.BodyPublishers;
|
||||||
import java.net.http.HttpResponse.BodyHandlers;
|
import java.net.http.HttpResponse.BodyHandlers;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class SessionServiceClient {
|
public class SessionServiceClient {
|
||||||
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5L);
|
private static final ExecutorService HTTP_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private final String sessionServiceUrl;
|
private final String sessionServiceUrl;
|
||||||
|
|
||||||
public SessionServiceClient(@Nonnull String sessionServiceUrl) {
|
public SessionServiceClient(@Nonnull String sessionServiceUrl) {
|
||||||
if (sessionServiceUrl != null && !sessionServiceUrl.isEmpty()) {
|
if (sessionServiceUrl != null && !sessionServiceUrl.isEmpty()) {
|
||||||
this.sessionServiceUrl = sessionServiceUrl.endsWith("/") ? sessionServiceUrl.substring(0, sessionServiceUrl.length() - 1) : sessionServiceUrl;
|
this.sessionServiceUrl = sessionServiceUrl.endsWith("/") ? sessionServiceUrl.substring(0, sessionServiceUrl.length() - 1) : sessionServiceUrl;
|
||||||
this.httpClient = HttpClient.newBuilder().connectTimeout(REQUEST_TIMEOUT).build();
|
this.httpClient = ServiceHttpClientFactory.create(AuthConfig.HTTP_TIMEOUT);
|
||||||
LOGGER.at(Level.INFO).log("Session Service client initialized for: %s", this.sessionServiceUrl);
|
LOGGER.at(Level.INFO).log("Session Service client initialized for: %s", this.sessionServiceUrl);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Session Service URL cannot be null or empty");
|
throw new IllegalArgumentException("Session Service URL cannot be null or empty");
|
||||||
@@ -49,7 +51,7 @@ public class SessionServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + bearerToken)
|
.header("Authorization", "Bearer " + bearerToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.POST(BodyPublishers.ofString(jsonBody))
|
.POST(BodyPublishers.ofString(jsonBody))
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Requesting authorization grant with identity token, aud='%s'", serverAudience);
|
LOGGER.at(Level.INFO).log("Requesting authorization grant with identity token, aud='%s'", serverAudience);
|
||||||
@@ -79,7 +81,8 @@ public class SessionServiceClient {
|
|||||||
LOGGER.at(Level.WARNING).log("Unexpected error requesting authorization grant: %s", var10.getMessage());
|
LOGGER.at(Level.WARNING).log("Unexpected error requesting authorization grant: %s", var10.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
HTTP_EXECUTOR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +101,7 @@ public class SessionServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + bearerToken)
|
.header("Authorization", "Bearer " + bearerToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.POST(BodyPublishers.ofString(jsonBody))
|
.POST(BodyPublishers.ofString(jsonBody))
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Exchanging authorization grant for access token");
|
LOGGER.at(Level.INFO).log("Exchanging authorization grant for access token");
|
||||||
@@ -130,7 +133,8 @@ public class SessionServiceClient {
|
|||||||
LOGGER.at(Level.WARNING).log("Unexpected error exchanging auth grant: %s", var10.getMessage());
|
LOGGER.at(Level.WARNING).log("Unexpected error exchanging auth grant: %s", var10.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
HTTP_EXECUTOR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +145,7 @@ public class SessionServiceClient {
|
|||||||
.uri(URI.create(this.sessionServiceUrl + "/.well-known/jwks.json"))
|
.uri(URI.create(this.sessionServiceUrl + "/.well-known/jwks.json"))
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.FINE).log("Fetching JWKS from Session Service");
|
LOGGER.at(Level.FINE).log("Fetching JWKS from Session Service");
|
||||||
@@ -181,7 +185,7 @@ public class SessionServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + oauthAccessToken)
|
.header("Authorization", "Bearer " + oauthAccessToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.GET()
|
.GET()
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Fetching game profiles...");
|
LOGGER.at(Level.INFO).log("Fetching game profiles...");
|
||||||
@@ -221,7 +225,7 @@ public class SessionServiceClient {
|
|||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("Authorization", "Bearer " + oauthAccessToken)
|
.header("Authorization", "Bearer " + oauthAccessToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.POST(BodyPublishers.ofString(body))
|
.POST(BodyPublishers.ofString(body))
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Creating game session...");
|
LOGGER.at(Level.INFO).log("Creating game session...");
|
||||||
@@ -262,7 +266,7 @@ public class SessionServiceClient {
|
|||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", "Bearer " + sessionToken)
|
.header("Authorization", "Bearer " + sessionToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.POST(BodyPublishers.noBody())
|
.POST(BodyPublishers.noBody())
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Refreshing game session...");
|
LOGGER.at(Level.INFO).log("Refreshing game session...");
|
||||||
@@ -292,7 +296,8 @@ public class SessionServiceClient {
|
|||||||
LOGGER.at(Level.WARNING).log("Unexpected error refreshing session: %s", var7.getMessage());
|
LOGGER.at(Level.WARNING).log("Unexpected error refreshing session: %s", var7.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
HTTP_EXECUTOR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +308,7 @@ public class SessionServiceClient {
|
|||||||
.uri(URI.create(this.sessionServiceUrl + "/game-session"))
|
.uri(URI.create(this.sessionServiceUrl + "/game-session"))
|
||||||
.header("Authorization", "Bearer " + sessionToken)
|
.header("Authorization", "Bearer " + sessionToken)
|
||||||
.header("User-Agent", AuthConfig.USER_AGENT)
|
.header("User-Agent", AuthConfig.USER_AGENT)
|
||||||
.timeout(REQUEST_TIMEOUT)
|
.timeout(AuthConfig.HTTP_TIMEOUT)
|
||||||
.DELETE()
|
.DELETE()
|
||||||
.build();
|
.build();
|
||||||
LOGGER.at(Level.INFO).log("Terminating game session...");
|
LOGGER.at(Level.INFO).log("Terminating game session...");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.google.gson.JsonParser;
|
|||||||
import com.hypixel.hytale.logger.HytaleLogger;
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
import com.hypixel.hytale.server.core.HytaleServer;
|
import com.hypixel.hytale.server.core.HytaleServer;
|
||||||
import com.hypixel.hytale.server.core.auth.AuthConfig;
|
import com.hypixel.hytale.server.core.auth.AuthConfig;
|
||||||
|
import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@@ -20,7 +21,6 @@ import java.net.http.HttpResponse.BodyHandlers;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -34,7 +34,7 @@ import javax.annotation.Nullable;
|
|||||||
public class OAuthClient {
|
public class OAuthClient {
|
||||||
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
private final HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10L)).build();
|
private final HttpClient httpClient = ServiceHttpClientFactory.create(AuthConfig.HTTP_TIMEOUT);
|
||||||
|
|
||||||
public Runnable startFlow(@Nonnull OAuthBrowserFlow flow) {
|
public Runnable startFlow(@Nonnull OAuthBrowserFlow flow) {
|
||||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.hypixel.hytale.function.function.TriFunction;
|
|||||||
import com.hypixel.hytale.logger.HytaleLogger;
|
import com.hypixel.hytale.logger.HytaleLogger;
|
||||||
import com.hypixel.hytale.math.util.ChunkUtil;
|
import com.hypixel.hytale.math.util.ChunkUtil;
|
||||||
import com.hypixel.hytale.math.vector.Vector4d;
|
import com.hypixel.hytale.math.vector.Vector4d;
|
||||||
import com.hypixel.hytale.metrics.metric.HistoricMetric;
|
|
||||||
import com.hypixel.hytale.protocol.BlockPosition;
|
import com.hypixel.hytale.protocol.BlockPosition;
|
||||||
import com.hypixel.hytale.protocol.ForkedChainId;
|
import com.hypixel.hytale.protocol.ForkedChainId;
|
||||||
import com.hypixel.hytale.protocol.GameMode;
|
import com.hypixel.hytale.protocol.GameMode;
|
||||||
@@ -44,11 +43,6 @@ import com.hypixel.hytale.server.core.universe.world.World;
|
|||||||
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
|
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
|
||||||
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
||||||
import com.hypixel.hytale.server.core.util.UUIDUtil;
|
import com.hypixel.hytale.server.core.util.UUIDUtil;
|
||||||
import io.sentry.Sentry;
|
|
||||||
import io.sentry.SentryEvent;
|
|
||||||
import io.sentry.SentryLevel;
|
|
||||||
import io.sentry.protocol.Message;
|
|
||||||
import io.sentry.protocol.SentryId;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
@@ -56,7 +50,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectList;
|
import it.unimi.dsi.fastutil.objects.ObjectList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -197,7 +190,7 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
int highestChainId = -1;
|
int highestChainId = -1;
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
label99:
|
label114:
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
SyncInteractionChain packet = it.next();
|
SyncInteractionChain packet = it.next();
|
||||||
if (packet.desync) {
|
if (packet.desync) {
|
||||||
@@ -233,7 +226,7 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
it.remove();
|
it.remove();
|
||||||
this.packetQueueTime = 0L;
|
this.packetQueueTime = 0L;
|
||||||
}
|
}
|
||||||
continue label99;
|
continue label114;
|
||||||
}
|
}
|
||||||
|
|
||||||
chain = subChain;
|
chain = subChain;
|
||||||
@@ -241,8 +234,20 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
highestChainId = Math.max(highestChainId, packet.chainId);
|
highestChainId = Math.max(highestChainId, packet.chainId);
|
||||||
if (chain == null && !finished) {
|
boolean isProxy = packet.data != null && !UUIDUtil.isEmptyOrNull(packet.data.proxyId);
|
||||||
if (this.syncStart(ref, packet)) {
|
if ((chain != null || finished) && !isProxy) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else if (this.syncStart(ref, packet)) {
|
||||||
changed = true;
|
changed = true;
|
||||||
it.remove();
|
it.remove();
|
||||||
this.packetQueueTime = 0L;
|
this.packetQueueTime = 0L;
|
||||||
@@ -271,21 +276,10 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!desynced) {
|
if (!desynced && !isProxy) {
|
||||||
finished = true;
|
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()) {
|
if (desynced && !packetQueue.isEmpty()) {
|
||||||
@@ -363,7 +357,7 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
long threshold = this.getOperationTimeoutThreshold();
|
long threshold = this.getOperationTimeoutThreshold();
|
||||||
TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType());
|
TimeResource timeResource = this.commandBuffer.getResource(TimeResource.getResourceType());
|
||||||
if (timeResource.getTimeDilationModifier() == 1.0F && waitMillis > threshold) {
|
if (timeResource.getTimeDilationModifier() == 1.0F && waitMillis > threshold) {
|
||||||
this.sendCancelPacket(chain);
|
this.cancelChains(chain);
|
||||||
return chain.getForkedChains().isEmpty();
|
return chain.getForkedChains().isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -641,41 +635,10 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
|
|
||||||
long threshold = this.getOperationTimeoutThreshold();
|
long threshold = this.getOperationTimeoutThreshold();
|
||||||
if (tickTimeDilation == 1.0F && waitMillis > threshold) {
|
if (tickTimeDilation == 1.0F && waitMillis > threshold) {
|
||||||
SentryEvent event = new SentryEvent();
|
LOGGER.atWarning().log("Client failed to send client data, ending early to prevent desync.");
|
||||||
event.setLevel(SentryLevel.ERROR);
|
|
||||||
Message message = new Message();
|
|
||||||
message.setMessage("Client failed to send client data, ending early to prevent desync");
|
|
||||||
HashMap<String, Object> unknown = new HashMap<>();
|
|
||||||
unknown.put("Threshold", threshold);
|
|
||||||
unknown.put("Wait Millis", waitMillis);
|
|
||||||
unknown.put("Current Root", chain.getRootInteraction() != null ? chain.getRootInteraction().getId() : "<null>");
|
|
||||||
Operation innerOp = operation.getInnerOperation();
|
|
||||||
unknown.put("Current Op", innerOp.getClass().getName());
|
|
||||||
if (innerOp instanceof Interaction interaction) {
|
|
||||||
unknown.put("Current Interaction", interaction.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
unknown.put("Current Index", chain.getOperationIndex());
|
|
||||||
unknown.put("Current Op Counter", chain.getOperationCounter());
|
|
||||||
HistoricMetric metric = ref.getStore().getExternalData().getWorld().getBufferedTickLengthMetricSet();
|
|
||||||
long[] periods = metric.getPeriodsNanos();
|
|
||||||
|
|
||||||
for (int i = 0; i < periods.length; i++) {
|
|
||||||
String length = FormatUtil.timeUnitToString(periods[i], TimeUnit.NANOSECONDS, true);
|
|
||||||
double average = metric.getAverage(i);
|
|
||||||
long min = metric.calculateMin(i);
|
|
||||||
long max = metric.calculateMax(i);
|
|
||||||
String value = FormatUtil.simpleTimeUnitFormat(min, average, max, TimeUnit.NANOSECONDS, TimeUnit.MILLISECONDS, 3);
|
|
||||||
unknown.put(String.format("World Perf %s", length), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
event.setExtras(unknown);
|
|
||||||
event.setMessage(message);
|
|
||||||
SentryId eventId = Sentry.captureEvent(event);
|
|
||||||
LOGGER.atWarning().log("Client failed to send client data, ending early to prevent desync. %s", eventId);
|
|
||||||
chain.setServerState(InteractionState.Failed);
|
chain.setServerState(InteractionState.Failed);
|
||||||
chain.setClientState(InteractionState.Failed);
|
chain.setClientState(InteractionState.Failed);
|
||||||
this.sendCancelPacket(chain);
|
this.cancelChains(chain);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
if (entry.consumeSendInitial() || wasWrong) {
|
if (entry.consumeSendInitial() || wasWrong) {
|
||||||
@@ -768,6 +731,8 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
if (ctx.isEnabled()) {
|
if (ctx.isEnabled()) {
|
||||||
ctx.log("Got syncStart for %d-%s but packet wasn't the first.", index, packet.forkedId);
|
ctx.log("Got syncStart for %d-%s but packet wasn't the first.", index, packet.forkedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sendCancelPacket(index, packet.forkedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -777,6 +742,7 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
ctx.log("Can't start a forked chain from the client: %d %s", index, packet.forkedId);
|
ctx.log("Can't start a forked chain from the client: %d %s", index, packet.forkedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sendCancelPacket(index, packet.forkedId);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
InteractionType type = packet.interactionType;
|
InteractionType type = packet.interactionType;
|
||||||
@@ -1362,7 +1328,7 @@ public class InteractionManager implements Component<EntityStore> {
|
|||||||
this.sendCancelPacket(chain.getChainId(), chain.getForkedChainId());
|
this.sendCancelPacket(chain.getChainId(), chain.getForkedChainId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendCancelPacket(int chainId, @Nonnull ForkedChainId forkedChainId) {
|
public void sendCancelPacket(int chainId, ForkedChainId forkedChainId) {
|
||||||
if (this.playerRef != null) {
|
if (this.playerRef != null) {
|
||||||
this.playerRef.getPacketHandler().writeNoCache(new CancelInteractionChain(chainId, forkedChainId));
|
this.playerRef.getPacketHandler().writeNoCache(new CancelInteractionChain(chainId, forkedChainId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -456,6 +456,9 @@ public class Inventory implements NetworkSerializable<UpdatePlayerInventory> {
|
|||||||
|
|
||||||
private boolean tryEquipArmorPart(int fromSectionId, short fromSlotId, int quantity, ItemContainer targetContainer, boolean forceEquip) {
|
private boolean tryEquipArmorPart(int fromSectionId, short fromSlotId, int quantity, ItemContainer targetContainer, boolean forceEquip) {
|
||||||
ItemStack itemStack = targetContainer.getItemStack(fromSlotId);
|
ItemStack itemStack = targetContainer.getItemStack(fromSlotId);
|
||||||
|
if (ItemStack.isEmpty(itemStack)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
Item item = itemStack.getItem();
|
Item item = itemStack.getItem();
|
||||||
ItemArmor itemArmor = item.getArmor();
|
ItemArmor itemArmor = item.getArmor();
|
||||||
if (itemArmor == null || fromSectionId == -3 || !forceEquip && this.armor.getItemStack((short)itemArmor.getArmorSlot().ordinal()) != null) {
|
if (itemArmor == null || fromSectionId == -3 || !forceEquip && this.armor.getItemStack((short)itemArmor.getArmorSlot().ordinal()) != null) {
|
||||||
@@ -465,6 +468,7 @@ public class Inventory implements NetworkSerializable<UpdatePlayerInventory> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public ListTransaction<MoveTransaction<ItemStackTransaction>> takeAll(int inventorySectionId) {
|
public ListTransaction<MoveTransaction<ItemStackTransaction>> takeAll(int inventorySectionId) {
|
||||||
|
|||||||
@@ -410,10 +410,12 @@ public abstract class PacketHandler implements IPacketReceiver {
|
|||||||
Attribute<Long> loginStartAttribute = channel.attr(LOGIN_START_ATTRIBUTE_KEY);
|
Attribute<Long> loginStartAttribute = channel.attr(LOGIN_START_ATTRIBUTE_KEY);
|
||||||
long now = System.nanoTime();
|
long now = System.nanoTime();
|
||||||
Long before = loginStartAttribute.getAndSet(now);
|
Long before = loginStartAttribute.getAndSet(now);
|
||||||
|
NettyUtil.TimeoutContext context = channel.attr(NettyUtil.TimeoutContext.KEY).get();
|
||||||
|
String identifier = context != null ? context.playerIdentifier() : NettyUtil.formatRemoteAddress(channel);
|
||||||
if (before == null) {
|
if (before == null) {
|
||||||
LOGIN_TIMING_LOGGER.at(level).log(message);
|
LOGIN_TIMING_LOGGER.at(level).log("[%s] %s", identifier, message);
|
||||||
} else {
|
} else {
|
||||||
LOGIN_TIMING_LOGGER.at(level).log("%s took %s", message, LazyArgs.lazy(() -> FormatUtil.nanosToString(now - before)));
|
LOGIN_TIMING_LOGGER.at(level).log("[%s] %s took %s", identifier, message, LazyArgs.lazy(() -> FormatUtil.nanosToString(now - before)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -688,6 +688,11 @@ public class Universe extends JavaPlugin implements IMessageReceiver, MetricProv
|
|||||||
.getEventBus()
|
.getEventBus()
|
||||||
.<Void, PlayerConnectEvent>dispatchFor(PlayerConnectEvent.class)
|
.<Void, PlayerConnectEvent>dispatchFor(PlayerConnectEvent.class)
|
||||||
.dispatch(new PlayerConnectEvent((Holder<EntityStore>)holder, playerRefComponent, lastWorld != null ? lastWorld : this.getDefaultWorld()));
|
.dispatch(new PlayerConnectEvent((Holder<EntityStore>)holder, playerRefComponent, lastWorld != null ? lastWorld : this.getDefaultWorld()));
|
||||||
|
if (!channel.isActive()) {
|
||||||
|
this.players.remove(uuid, playerRefComponent);
|
||||||
|
this.getLogger().at(Level.INFO).log("Player '%s' (%s) disconnected during PlayerConnectEvent, cleaned up", username, uuid);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
} else {
|
||||||
World world = event.getWorld() != null ? event.getWorld() : this.getDefaultWorld();
|
World world = event.getWorld() != null ? event.getWorld() : this.getDefaultWorld();
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
this.players.remove(uuid, playerRefComponent);
|
this.players.remove(uuid, playerRefComponent);
|
||||||
@@ -703,7 +708,14 @@ public class Universe extends JavaPlugin implements IMessageReceiver, MetricProv
|
|||||||
|
|
||||||
PacketHandler.logConnectionTimings(channel, "Processed Referral", Level.FINEST);
|
PacketHandler.logConnectionTimings(channel, "Processed Referral", Level.FINEST);
|
||||||
playerRefComponent.getPacketHandler().write(new ServerTags(AssetRegistry.getClientTags()));
|
playerRefComponent.getPacketHandler().write(new ServerTags(AssetRegistry.getClientTags()));
|
||||||
return world.addPlayer(playerRefComponent, null, false, false).thenApply(p -> {
|
CompletableFuture<PlayerRef> addPlayerFuture = world.addPlayer(playerRefComponent, null, false, false);
|
||||||
|
if (addPlayerFuture == null) {
|
||||||
|
this.players.remove(uuid, playerRefComponent);
|
||||||
|
this.getLogger().at(Level.INFO).log("Player '%s' (%s) disconnected before world addition, cleaned up", username, uuid);
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
} else {
|
||||||
|
return addPlayerFuture.<PlayerRef>thenApply(
|
||||||
|
p -> {
|
||||||
PacketHandler.logConnectionTimings(channel, "Add to World", Level.FINEST);
|
PacketHandler.logConnectionTimings(channel, "Add to World", Level.FINEST);
|
||||||
if (!channel.isActive()) {
|
if (!channel.isActive()) {
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
@@ -711,7 +723,9 @@ public class Universe extends JavaPlugin implements IMessageReceiver, MetricProv
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.players.remove(uuid, playerRefComponent);
|
this.players.remove(uuid, playerRefComponent);
|
||||||
this.getLogger().at(Level.WARNING).log("Player '%s' (%s) disconnected during world join, cleaned up from universe", username, uuid);
|
this.getLogger()
|
||||||
|
.at(Level.WARNING)
|
||||||
|
.log("Player '%s' (%s) disconnected during world join, cleaned up from universe", username, uuid);
|
||||||
return null;
|
return null;
|
||||||
} else if (playerComponent.wasRemoved()) {
|
} else if (playerComponent.wasRemoved()) {
|
||||||
this.players.remove(uuid, playerRefComponent);
|
this.players.remove(uuid, playerRefComponent);
|
||||||
@@ -719,7 +733,9 @@ public class Universe extends JavaPlugin implements IMessageReceiver, MetricProv
|
|||||||
} else {
|
} else {
|
||||||
return (PlayerRef)p;
|
return (PlayerRef)p;
|
||||||
}
|
}
|
||||||
}).exceptionally(throwable -> {
|
}
|
||||||
|
)
|
||||||
|
.exceptionally(throwable -> {
|
||||||
this.players.remove(uuid, playerRefComponent);
|
this.players.remove(uuid, playerRefComponent);
|
||||||
playerComponent.remove();
|
playerComponent.remove();
|
||||||
throw new RuntimeException("Exception when adding player to universe:", throwable);
|
throw new RuntimeException("Exception when adding player to universe:", throwable);
|
||||||
@@ -727,6 +743,8 @@ public class Universe extends JavaPlugin implements IMessageReceiver, MetricProv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class IndexedStorageChunkStorageProvider implements IChunkStorageProvider
|
|||||||
)
|
)
|
||||||
.add()
|
.add()
|
||||||
.build();
|
.build();
|
||||||
private boolean flushOnWrite = true;
|
private boolean flushOnWrite = false;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.HytaleServer;
|
|||||||
import com.hypixel.hytale.server.core.HytaleServerConfig;
|
import com.hypixel.hytale.server.core.HytaleServerConfig;
|
||||||
import com.hypixel.hytale.server.core.auth.AuthConfig;
|
import com.hypixel.hytale.server.core.auth.AuthConfig;
|
||||||
import com.hypixel.hytale.server.core.auth.ServerAuthManager;
|
import com.hypixel.hytale.server.core.auth.ServerAuthManager;
|
||||||
|
import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
|
||||||
import com.hypixel.hytale.server.core.util.io.FileUtil;
|
import com.hypixel.hytale.server.core.util.io.FileUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -46,7 +47,7 @@ public class UpdateService {
|
|||||||
private final String accountDataUrl = "https://account-data.hytale.com";
|
private final String accountDataUrl = "https://account-data.hytale.com";
|
||||||
|
|
||||||
public UpdateService() {
|
public UpdateService() {
|
||||||
this.httpClient = HttpClient.newBuilder().connectTimeout(REQUEST_TIMEOUT).followRedirects(Redirect.NORMAL).build();
|
this.httpClient = ServiceHttpClientFactory.newBuilder(REQUEST_TIMEOUT).followRedirects(Redirect.NORMAL).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.hypixel.hytale.server.core.util;
|
||||||
|
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpClient.Builder;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public final class ServiceHttpClientFactory {
|
||||||
|
private ServiceHttpClientFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static Builder newBuilder(@Nonnull Duration connectTimeout) {
|
||||||
|
Objects.requireNonNull(connectTimeout, "connectTimeout");
|
||||||
|
return HttpClient.newBuilder().connectTimeout(connectTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static HttpClient create(@Nonnull Duration connectTimeout) {
|
||||||
|
return newBuilder(connectTimeout).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user