package com.hypixel.hytale.builtin.deployables.interaction; import com.hypixel.hytale.builtin.deployables.DeployablesUtils; import com.hypixel.hytale.builtin.deployables.config.DeployableConfig; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; import com.hypixel.hytale.codec.codecs.map.Object2FloatMapCodec; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentAccessor; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.math.vector.Vector3f; import com.hypixel.hytale.protocol.Direction; import com.hypixel.hytale.protocol.Interaction; import com.hypixel.hytale.protocol.InteractionState; import com.hypixel.hytale.protocol.InteractionSyncData; import com.hypixel.hytale.protocol.InteractionType; import com.hypixel.hytale.protocol.Position; import com.hypixel.hytale.protocol.WaitForDataFrom; import com.hypixel.hytale.server.core.entity.InteractionContext; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap; import com.hypixel.hytale.server.core.modules.entitystats.EntityStatValue; import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType; import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler; import com.hypixel.hytale.server.core.modules.interaction.interaction.config.SimpleInstantInteraction; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import it.unimi.dsi.fastutil.ints.Int2FloatMap; import it.unimi.dsi.fastutil.ints.Int2FloatMap.Entry; import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import javax.annotation.Nonnull; import org.checkerframework.checker.nullness.compatqual.NonNullDecl; public class SpawnDeployableFromRaycastInteraction extends SimpleInstantInteraction { @Nonnull public static final BuilderCodec CODEC = BuilderCodec.builder( SpawnDeployableFromRaycastInteraction.class, SpawnDeployableFromRaycastInteraction::new, SimpleInstantInteraction.CODEC ) .append(new KeyedCodec<>("Config", DeployableConfig.CODEC), (i, s) -> i.config = s, i -> i.config) .addValidator(Validators.nonNull()) .add() .>append( new KeyedCodec<>("PreviewStatConditions", new Object2FloatMapCodec<>(Codec.STRING, Object2FloatOpenHashMap::new)), (changeStatInteraction, stringObject2DoubleMap) -> changeStatInteraction.unknownEntityStats = stringObject2DoubleMap, changeStatInteraction -> changeStatInteraction.unknownEntityStats ) .addValidator(EntityStatType.VALIDATOR_CACHE.getMapKeyValidator()) .documentation("Modifiers to apply to EntityStats.") .add() .appendInherited( new KeyedCodec<>("MaxPlacementDistance", Codec.FLOAT), (o, i) -> o.maxPlacementDistance = i, o -> o.maxPlacementDistance, (i, o) -> i.maxPlacementDistance = o.maxPlacementDistance ) .documentation("The max distance at which the player can deploy the deployable.") .add() .afterDecode(SpawnDeployableFromRaycastInteraction::processConfig) .build(); protected Object2FloatMap unknownEntityStats; protected Int2FloatMap entityStats; protected float maxPlacementDistance; private DeployableConfig config; private void processConfig() { if (this.unknownEntityStats != null) { this.entityStats = EntityStatsModule.resolveEntityStats(this.unknownEntityStats); } } private static boolean isSurface(@Nonnull Vector3f normal) { return normal.x == 0.0F && normal.y - 1.0F < 0.01 && normal.z == 0.0F; } @Override public boolean needsRemoteSync() { return true; } @NonNullDecl @Override public WaitForDataFrom getWaitForDataFrom() { return WaitForDataFrom.Client; } @Override protected void firstRun(@Nonnull InteractionType type, @Nonnull InteractionContext context, @Nonnull CooldownHandler cooldownHandler) { Ref entityRef = context.getOwningEntity(); Store store = entityRef.getStore(); CommandBuffer commandBuffer = context.getCommandBuffer(); assert commandBuffer != null; InteractionSyncData clientState = context.getClientState(); assert clientState != null; if (!this.canAfford(context.getEntity(), commandBuffer)) { context.getState().state = InteractionState.Failed; } else { Position raycastHit = clientState.raycastHit; if (raycastHit == null) { TransformComponent transformComponent = store.getComponent(entityRef, TransformComponent.getComponentType()); assert transformComponent != null; Vector3d position = transformComponent.getPosition(); raycastHit = new Position((float)position.x, (float)position.y, (float)position.z); } com.hypixel.hytale.protocol.Vector3f raycastNormal = clientState.raycastNormal; float correctedRaycastDistance = clientState.raycastDistance; com.hypixel.hytale.protocol.Vector3f spawnPosition = new com.hypixel.hytale.protocol.Vector3f( (float)raycastHit.x, (float)raycastHit.y, (float)raycastHit.z ); Vector3f norm = new Vector3f(raycastNormal.x, raycastNormal.y, raycastNormal.z); if (correctedRaycastDistance > 0.0F && correctedRaycastDistance <= this.maxPlacementDistance && (this.config.getAllowPlaceOnWalls() || isSurface(norm))) { Direction attackerRot = clientState.attackerRot; Vector3f rot = new Vector3f(0.0F, attackerRot.yaw, 0.0F); DeployablesUtils.spawnDeployable( commandBuffer, store, this.config, entityRef, new Vector3f(spawnPosition.x, spawnPosition.y, spawnPosition.z), rot, "UP" ); } } } protected boolean canAfford(@Nonnull Ref entityRef, @Nonnull ComponentAccessor componentAccessor) { if (this.entityStats != null && !this.entityStats.isEmpty()) { EntityStatMap entityStatMapComponent = componentAccessor.getComponent(entityRef, EntityStatMap.getComponentType()); if (entityStatMapComponent == null) { return false; } else { ObjectIterator var4 = this.entityStats.int2FloatEntrySet().iterator(); while (var4.hasNext()) { Entry cost = (Entry)var4.next(); EntityStatValue stat = entityStatMapComponent.get(cost.getIntKey()); if (stat == null || stat.get() < cost.getFloatValue()) { return false; } } return true; } } else { return true; } } @NonNullDecl @Override protected Interaction generatePacket() { return new com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction(); } @Override protected void configurePacket(Interaction packet) { super.configurePacket(packet); com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction p = (com.hypixel.hytale.protocol.SpawnDeployableFromRaycastInteraction)packet; p.deployableConfig = this.config.toPacket(); p.maxDistance = this.maxPlacementDistance; p.costs = this.entityStats; } }