WeightedNPCSpawn class
Пакет: com.hypixel.hytale.server.npc.interactions
Файл: com/hypixel/hytale/server/npc/interactions/SpawnNPCInteraction.java
implements: IWeightedElement
Поля (5)
| Модификаторы | Тип | Имя |
|---|---|---|
|
private static final BuilderCodec<SpawnNPCInteraction.WeightedNPCSpawn> |
CODEC |
|
private String |
id |
|
private int |
maxCount |
|
private int |
minCount |
|
private double |
weight |
Методы (1)
| Модификаторы | Возврат | Сигнатура |
|---|---|---|
|
public double |
getWeightpublic double getWeight() |
Исходный код
Показать/скрыть
class="kw">package com.hypixel.hytale.server.npc.interactions;
class="kw">import com.hypixel.hytale.codec.Codec;
class="kw">import com.hypixel.hytale.codec.KeyedCodec;
class="kw">import com.hypixel.hytale.codec.builder.BuilderCodec;
class="kw">import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
class="kw">import com.hypixel.hytale.codec.validation.Validators;
class="kw">import com.hypixel.hytale.codec.validation.validator.DoubleArrayValidator;
class="kw">import com.hypixel.hytale.codec.validation.validator.IntArrayValidator;
class="kw">import com.hypixel.hytale.common.map.IWeightedElement;
class="kw">import com.hypixel.hytale.common.map.IWeightedMap;
class="kw">import com.hypixel.hytale.common.map.WeightedMap;
class="kw">import com.hypixel.hytale.component.CommandBuffer;
class="kw">import com.hypixel.hytale.component.Ref;
class="kw">import com.hypixel.hytale.component.Store;
class="kw">import com.hypixel.hytale.math.util.ChunkUtil;
class="kw">import com.hypixel.hytale.math.util.MathUtil;
class="kw">import com.hypixel.hytale.math.vector.Rotation3f;
class="kw">import com.hypixel.hytale.math.vector.Vector3dUtil;
class="kw">import com.hypixel.hytale.protocol.InteractionState;
class="kw">import com.hypixel.hytale.protocol.InteractionType;
class="kw">import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
class="kw">import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
class="kw">import com.hypixel.hytale.server.core.entity.InteractionContext;
class="kw">import com.hypixel.hytale.server.core.inventory.ItemStack;
class="kw">import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
class="kw">import com.hypixel.hytale.server.core.modules.interaction.interaction.CooldownHandler;
class="kw">import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
class="kw">import com.hypixel.hytale.server.core.modules.interaction.interaction.config.client.SimpleBlockInteraction;
class="kw">import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
class="kw">import com.hypixel.hytale.server.core.universe.world.World;
class="kw">import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
class="kw">import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
class="kw">import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
class="kw">import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
class="kw">import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
class="kw">import com.hypixel.hytale.server.npc.NPCPlugin;
class="kw">import com.hypixel.hytale.server.npc.entities.NPCEntity;
class="kw">import com.hypixel.hytale.server.npc.role.Role;
class="kw">import com.hypixel.hytale.server.npc.role.support.StateSupport;
class="kw">import com.hypixel.hytale.server.npc.validators.NPCRoleValidator;
class="kw">import com.hypixel.hytale.server.spawning.SpawnTestResult;
class="kw">import java.util.concurrent.ThreadLocalRandom;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">import org.joml.Vector3d;
class="kw">import org.joml.Vector3i;
class="kw">public class SpawnNPCInteraction class="kw">extends SimpleBlockInteraction {
class="kw">private class="kw">static class="kw">final int MAX_SPAWN_COUNT = 100;
@Nonnull
class="kw">public class="kw">static class="kw">final BuilderCodec<SpawnNPCInteraction> CODEC = BuilderCodec.builder(
SpawnNPCInteraction.class, SpawnNPCInteraction::new, SimpleBlockInteraction.CODEC
)
.documentation("Spawns an NPC on the block that is being interacted with.")
.<String>append(new KeyedCodec<>("EntityId", Codec.STRING), (var0, var1) -> var0.entityId = var1, var0 -> var0.entityId)
.documentation("The ID of the entity asset to spawn.")
.addValidator(NPCRoleValidator.INSTANCE)
.add()
.<SpawnNPCInteraction.WeightedNPCSpawn[]>append(
new KeyedCodec<>("WeightedEntityIds", new ArrayCodec<>(SpawnNPCInteraction.WeightedNPCSpawn.CODEC, SpawnNPCInteraction.WeightedNPCSpawn[]::new)),
(var0, var1) -> var0.weightedSpawns = var1,
var0 -> var0.weightedSpawns
)
.documentation(
"A weighted list of entity IDs from which one is selected per spawn roll. Each entry has its own CountRange. Supersedes any provided EntityId. The number of weighted rolls is controlled by SpawnCount (class="kw">default 1)."
)
.add()
.<Vector3d>append(new KeyedCodec<>("SpawnOffset", Vector3dUtil.CODEC), (var0, var1) -> var0.spawnOffset.set(var1), var0 -> var0.spawnOffset)
.documentation("The offset to apply to the spawn position of the NPC, relative to the block's rotation and center.")
.add()
.<Float>append(new KeyedCodec<>("SpawnYawOffset", Codec.FLOAT), (var0, var1) -> var0.spawnYawOffset = var1, var0 -> var0.spawnYawOffset)
.documentation("The yaw rotation offset in radians to apply to the NPC rotation, relative to the block's yaw.")
.add()
.<Float>append(new KeyedCodec<>("SpawnChance", Codec.FLOAT), (var0, var1) -> var0.spawnChance = var1, var0 -> var0.spawnChance)
.documentation("The chance of the NPC spawning when the interaction is triggered.")
.add()
.<Integer>append(
new KeyedCodec<>("AlternateSpawnMaxSearchDistance", Codec.INTEGER),
(var0, var1) -> var0.alternateSpawnMaxSearchDistance = var1,
var0 -> var0.alternateSpawnMaxSearchDistance
)
.documentation(
"When the primary spawn position has no space (FAIL_INVALID_POSITION), try adjacent world columns along the horizontal cardinal axis toward the interacting player, up to this many block steps from the primary column. 0 disables alternate search. Distance is along that axis only, not a Euclidean radius."
)
.addValidator(Validators.min(0))
.add()
.<int[]>append(new KeyedCodec<>("SpawnCount", Codec.INT_ARRAY), (var0, var1) -> {
var0.minSpawnCount = var1[0];
var0.maxSpawnCount = var1[1];
}, var0 -> var0.minSpawnCount == 1 && var0.maxSpawnCount == 1 ? null : new int[]{var0.minSpawnCount, var0.maxSpawnCount})
.documentation(
"Range [min, max] of spawns per interaction trigger. With WeightedEntityIds this is the number of weighted rolls (each roll spawns the picked entry's own CountRange). Set min and max to the same value for an exact count."
)
.addValidator(Validators.intArraySize(2))
.addValidator(new IntArrayValidator(Validators.range(1, 100)))
.addValidator(Validators.weaklyMonotonicSequentialIntArrayValidator())
.add()
.<double[]>append(new KeyedCodec<>("DistanceRange", Codec.DOUBLE_ARRAY), (var0, var1) -> {
var0.minDistance = var1[0];
var0.maxDistance = var1[1];
}, var0 -> var0.minDistance == 0.0 && var0.maxDistance == 0.0 ? null : new double[]{var0.minDistance, var0.maxDistance})
.documentation(
"Range [min, max] of random horizontal distance from the spawn offset to scatter each NPC. Applied per NPC when SpawnCount resolves to more than one. 0 disables scatter."
)
.addValidator(Validators.doubleArraySize(2))
.addValidator(new DoubleArrayValidator(Validators.min(0.0)))
.addValidator(Validators.weaklyMonotonicSequentialDoubleArrayValidator())
.add()
.<String>append(new KeyedCodec<>("SpawnState", Codec.STRING), (var0, var1) -> var0.spawnState = var1, var0 -> var0.spawnState)
.documentation("An optional state to set on the spawned NPC if it exists.")
.add()
.<String>append(new KeyedCodec<>("SpawnSubState", Codec.STRING), (var0, var1) -> var0.spawnSubState = var1, var0 -> var0.spawnSubState)
.documentation("An optional sub-state to set on the spawned NPC if it exists. Only used when SpawnState is also set.")
.add()
.<Double>append(
new KeyedCodec<>("SpawnVelocity", Codec.DOUBLE),
(var0, var1) -> var0.spawnVelocity = var1,
var0 -> var0.spawnVelocity == 0.0 ? null : var0.spawnVelocity
)
.documentation("Random horizontal velocity magnitude applied to each spawned NPC in a random XZ direction. 0 disables.")
.add()
.<Boolean>append(
new KeyedCodec<>("AllowMidAirSpawn", Codec.BOOLEAN), (var0, var1) -> var0.allowMidAirSpawn = var1, var0 -> var0.allowMidAirSpawn ? Boolean.TRUE : null
)
.documentation(
"When true, still validates physical space but skips the ground-seeking alternate column search on failure, allowing NPCs to spawn mid-air (e.g. from hanging cocoons). Defaults to false."
)
.add()
.<Boolean>append(
new KeyedCodec<>("CenterHitboxOnPosition", Codec.BOOLEAN),
(var0, var1) -> var0.centerHitboxOnPosition = var1,
var0 -> var0.centerHitboxOnPosition ? Boolean.TRUE : null
)
.documentation(
"When true, the spawned NPC's collision box is centered on the computed spawn position (including filler cells for block targets) rather than placing its feet there. Useful for NPCs emerging from hanging blocks such as cocoons. Defaults to false."
)
.add()
.<Boolean>append(
new KeyedCodec<>("RequireFullCubeClearance", Codec.BOOLEAN),
(var0, var1) -> var0.requireFullCubeClearance = var1,
var0 -> var0.requireFullCubeClearance ? null : Boolean.FALSE
)
.documentation(
"When true (class="kw">default), every non-air block blocks the spawn position. When false, decorative non-cube blocks (ropes, plants, cocoons, etc.) are ignored and only full-cube blocks block it, letting NPCs emerge from hanging blocks such as cocoons."
)
.add()
.afterDecode(var0 -> {
if (var0.weightedSpawns != null && var0.weightedSpawns.length > 0) {
WeightedMap.Builder var1 = WeightedMap.builder(new SpawnNPCInteraction.WeightedNPCSpawn[0]);
for (SpawnNPCInteraction.WeightedNPCSpawn var5 : var0.weightedSpawns) {
var1.put(var5, var5.weight);
}
var0.weightedSpawnMap = var1.build();
}
})
.build();
class="kw">protected String entityId;
class="kw">protected SpawnNPCInteraction.WeightedNPCSpawn[] weightedSpawns;
class="kw">protected IWeightedMap<SpawnNPCInteraction.WeightedNPCSpawn> weightedSpawnMap;
@Nonnull
class="kw">protected Vector3d spawnOffset = new Vector3d();
class="kw">protected float spawnYawOffset;
class="kw">protected float spawnChance = 1.0F;
class="kw">protected int alternateSpawnMaxSearchDistance;
class="kw">protected int minSpawnCount = 1;
class="kw">protected int maxSpawnCount = 1;
class="kw">protected double minDistance;
class="kw">protected double maxDistance;
class="kw">protected double spawnVelocity;
class="kw">protected boolean allowMidAirSpawn;
class="kw">protected boolean centerHitboxOnPosition;
class="kw">protected boolean requireFullCubeClearance = true;
@Nullable
class="kw">protected String spawnState;
@Nullable
class="kw">protected String spawnSubState;
class="kw">public SpawnNPCInteraction() {
}
class="kw">private void spawnNPC(@Nonnull CommandBuffer<EntityStore> var1, @Nonnull InteractionContext var2, @Nonnull Vector3i var3) {
int var4 = this.minSpawnCount == this.maxSpawnCount
? this.minSpawnCount
: ThreadLocalRandom.current().nextInt(this.minSpawnCount, this.maxSpawnCount + 1);
for (int var5 = 0; var5 < var4; var5++) {
if (this.weightedSpawnMap != null) {
SpawnNPCInteraction.WeightedNPCSpawn var6 = this.weightedSpawnMap.get(ThreadLocalRandom.current());
if (var6 != null) {
int var7 = var6.minCount == var6.maxCount ? var6.minCount : ThreadLocalRandom.current().nextInt(var6.minCount, var6.maxCount + 1);
for (int var8 = 0; var8 < var7; var8++) {
this.spawnSingleNPC(var1, var2, var3, var6.id);
}
}
} else {
this.spawnSingleNPC(var1, var2, var3, this.entityId);
}
}
}
class="kw">private void spawnSingleNPC(@Nonnull CommandBuffer<EntityStore> var1, @Nonnull InteractionContext var2, @Nonnull Vector3i var3, @Nullable String var4) {
if (var4 != null) {
Store var5 = var1.getStore();
World var6 = var5.getExternalData().getWorld();
SpawnNPCInteraction.SpawnData var7 = this.computeSpawnData(var6, var3, var2);
if (this.maxDistance > 0.0) {
ThreadLocalRandom var8 = ThreadLocalRandom.current();
double var9 = this.minDistance == this.maxDistance ? this.minDistance : var8.nextDouble(this.minDistance, this.maxDistance);
double var11 = var8.nextDouble(Math.PI * 2);
var7.position().add(Math.cos(var11) * var9, 0.0, Math.sin(var11) * var9);
}
SpawnTestResult var23 = NPCPlugin.get()
.spawnNPCWithSpaceValidation(
var5, var4, null, var7.position(), var7.rotation(), this::postSpawn, !this.requireFullCubeClearance, this.centerHitboxOnPosition
);
if (var23 != SpawnTestResult.TEST_OK) {
if (!this.allowMidAirSpawn && var23 == SpawnTestResult.FAIL_INVALID_POSITION && this.alternateSpawnMaxSearchDistance > 0) {
Ref var24 = var2.getOwningEntity();
Ref var10 = var24 != null && var24.isValid() ? var24 : var2.getEntity();
if (var10 != null && var10.isValid()) {
TransformComponent var26 = var1.getComponent(var10, TransformComponent.getComponentType());
if (var26 != null) {
Vector3d var12 = var26.getPosition();
Vector3d var13 = var7.position();
int var14 = MathUtil.floor(var13.x);
int var15 = MathUtil.floor(var13.z);
int var16;
int var17;
if (Math.abs(var12.x() - var13.x) >= Math.abs(var12.z() - var13.z)) {
var16 = var12.x() >= var13.x ? 1 : -1;
var17 = 0;
} else {
var16 = 0;
var17 = var12.z() >= var13.z ? 1 : -1;
}
Rotation3f var18 = new Rotation3f(0.0F, PhysicsMath.headingFromDirection(var16, var17), 0.0F);
for (int var19 = 1; var19 <= this.alternateSpawnMaxSearchDistance; var19++) {
int var20 = var14 + var19 * var16;
int var21 = var15 + var19 * var17;
SpawnTestResult var22 = NPCPlugin.get()
.spawnNPCWithColumnProbe(var5, var4, null, var6, var20, var21, var13.y(), var18, this::postSpawn);
if (var22 == SpawnTestResult.TEST_OK) {
class="kw">return;
}
if (var22 == SpawnTestResult.FAIL_SPAWN) {
break;
}
}
}
}
}
SpawnNPCInteractionFailureTracker var25 = var1.getResource(SpawnNPCInteractionFailureTracker.getResourceType());
if (var25.recordFailure(var3)) {
if (var23 == SpawnTestResult.FAIL_INVALID_POSITION) {
NPCPlugin.get()
.getLogger()
.at(Level.WARNING)
.log("Failed to spawn NPC with Entity ID '%s' at block %d/%d/%d, because there isn't enough space.", var4, var3.x, var3.y, var3.z);
} else {
NPCPlugin.get()
.getLogger()
.at(Level.WARNING)
.log("Failed to spawn NPC with Entity ID '%s' at block %d/%d/%d (%s).", var4, var3.x, var3.y, var3.z, var23);
}
}
}
}
}
class="kw">private void postSpawn(@Nonnull NPCEntity var1, @Nonnull Ref<EntityStore> var2, @Nonnull Store<EntityStore> var3) {
if (this.spawnState != null) {
StateSupport.get(var2, var3).setState(var2, this.spawnState, this.spawnSubState, var3);
}
if (this.spawnVelocity > 0.0) {
Role var4 = var1.getRole();
if (var4 != null) {
double var5 = ThreadLocalRandom.current().nextDouble(Math.PI * 2);
var4.setVelocity(new Vector3d(Math.cos(var5) * this.spawnVelocity, 0.0, Math.sin(var5) * this.spawnVelocity), null, true);
}
}
}
@Nonnull
class="kw">private SpawnNPCInteraction.SpawnData computeSpawnData(@Nonnull World var1, @Nonnull Vector3i var2, @Nonnull InteractionContext var3) {
BlockType var4 = var3.getMetaStore().getIfPresentMetaObject(Interaction.TARGET_BLOCK_TYPE);
if (var4 != null) {
int var14 = var3.getMetaStore().getMetaObject(Interaction.TARGET_BLOCK_ROTATION_INDEX);
class="kw">return this.computeSpawnDataForRotation(var2, var4, var14);
}
long var5 = ChunkUtil.indexChunkFromBlock(var2.x, var2.z);
ChunkStore var7 = var1.getChunkStore();
Ref var8 = var7.getChunkReference(var5);
if (var8 != null && var8.isValid()) {
WorldChunk var9 = var7.getStore().getComponent(var8, WorldChunk.getComponentType());
assert var9 != null;
BlockType var10 = var9.getBlockType(var2.x, var2.y, var2.z);
if (var10 == null) {
class="kw">return this.computeSpawnDataFromContextRotation(var2, var3);
}
BlockChunk var11 = var7.getStore().getComponent(var8, BlockChunk.getComponentType());
if (var11 == null) {
class="kw">return this.computeSpawnDataFromContextRotation(var2, var3);
}
BlockSection var12 = var11.getSectionAtBlockY(var2.y);
int var13 = var12.getRotationIndex(var2.x, var2.y, var2.z);
class="kw">return this.computeSpawnDataForRotation(var2, var10, var13);
} else {
class="kw">return this.computeSpawnDataFromContextRotation(var2, var3);
}
}
@Nonnull
class="kw">private SpawnNPCInteraction.SpawnData computeSpawnDataFromContextRotation(@Nonnull Vector3i var1, @Nonnull InteractionContext var2) {
int var3 = var2.getMetaStore().getMetaObject(Interaction.TARGET_BLOCK_ROTATION_INDEX);
class="kw">return this.computeSpawnDataFromRotation(var1, var3);
}
@Nonnull
class="kw">private SpawnNPCInteraction.SpawnData computeSpawnDataFromRotation(@Nonnull Vector3i var1, int var2) {
RotationTuple var3 = RotationTuple.get(var2);
Vector3d var4 = var3.rotatedVector(this.spawnOffset);
var4.add(var1.x + 0.5, var1.y + 0.5, var1.z + 0.5);
Rotation3f var5 = new Rotation3f(0.0F, (float)(var3.yaw().getRadians() + Math.toRadians(this.spawnYawOffset)), 0.0F);
class="kw">return new SpawnNPCInteraction.SpawnData(var4, var5);
}
@Nonnull
class="kw">private SpawnNPCInteraction.SpawnData computeSpawnDataForRotation(@Nonnull Vector3i var1, @Nonnull BlockType var2, int var3) {
RotationTuple var4 = RotationTuple.get(var3);
Vector3d var5 = var4.rotatedVector(this.spawnOffset);
Vector3d var6 = new Vector3d();
var2.getBlockCenter(var3, var6);
var5.add(var6).add(var1.x, var1.y, var1.z);
Rotation3f var7 = new Rotation3f(0.0F, (float)(var4.yaw().getRadians() + Math.toRadians(this.spawnYawOffset)), 0.0F);
class="kw">return new SpawnNPCInteraction.SpawnData(var5, var7);
}
@Override
class="kw">protected void interactWithBlock(
@Nonnull World var1,
@Nonnull CommandBuffer<EntityStore> var2,
@Nonnull InteractionType var3,
@Nonnull InteractionContext var4,
@Nullable ItemStack var5,
@Nonnull Vector3i var6,
@Nonnull CooldownHandler var7
) {
if (ThreadLocalRandom.current().nextFloat() > this.spawnChance) {
var4.getState().state = InteractionState.Failed;
} else {
var2.run(var4x -> this.spawnNPC(var2, var4, var6));
}
}
@Override
class="kw">protected void simulateInteractWithBlock(
@Nonnull InteractionType var1, @Nonnull InteractionContext var2, @Nullable ItemStack var3, @Nonnull World var4, @Nonnull Vector3i var5
) {
}
class="kw">private record SpawnData(@Nonnull Vector3d position, @Nonnull Rotation3f rotation) {
class="kw">private SpawnData {
}
}
class="kw">protected class="kw">static class WeightedNPCSpawn class="kw">implements IWeightedElement {
class="kw">private class="kw">static class="kw">final BuilderCodec<SpawnNPCInteraction.WeightedNPCSpawn> CODEC = BuilderCodec.builder(
SpawnNPCInteraction.WeightedNPCSpawn.class, SpawnNPCInteraction.WeightedNPCSpawn::new
)
.append(new KeyedCodec<>("Id", Codec.STRING), (var0, var1) -> var0.id = var1, var0 -> var0.id)
.documentation("The Role ID of the NPC to spawn.")
.addValidator(Validators.nonNull())
.addValidator(NPCRoleValidator.INSTANCE)
.add()
.<Double>append(new KeyedCodec<>("Weight", Codec.DOUBLE, true), (var0, var1) -> var0.weight = var1, var0 -> var0.weight)
.documentation("The relative weight of this NPC (chance of being spawned is this value relative to the sum of all weights).")
.addValidator(Validators.nonNull())
.addValidator(Validators.greaterThan(0.0))
.add()
.<int[]>append(new KeyedCodec<>("CountRange", Codec.INT_ARRAY), (var0, var1) -> {
var0.minCount = var1[0];
var0.maxCount = var1[1];
}, var0 -> var0.minCount == 1 && var0.maxCount == 1 ? null : new int[]{var0.minCount, var0.maxCount})
.documentation("Range [min, max] of this NPC to spawn when this entry is picked. Set min and max to the same value for an exact count.")
.addValidator(Validators.intArraySize(2))
.addValidator(new IntArrayValidator(Validators.range(1, 100)))
.addValidator(Validators.weaklyMonotonicSequentialIntArrayValidator())
.add()
.build();
class="kw">private String id;
class="kw">private double weight;
class="kw">private int minCount = 1;
class="kw">private int maxCount = 1;
class="kw">private WeightedNPCSpawn() {
}
@Override
class="kw">public double getWeight() {
class="kw">return this.weight;
}
}
}