package com.hypixel.hytale.server.npc.commands; import com.hypixel.hytale.codec.validation.Validators; import com.hypixel.hytale.common.util.RandomUtil; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Holder; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.function.consumer.TriConsumer; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.math.util.MathUtil; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.math.vector.Vector3f; import com.hypixel.hytale.protocol.PlayerSkin; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.asset.type.model.config.Model; import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes; import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand; import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException; import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; import com.hypixel.hytale.server.core.entity.Frozen; import com.hypixel.hytale.server.core.entity.UUIDComponent; import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.entity.player.ApplyRandomSkinPersistedComponent; import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.flock.FlockPlugin; import com.hypixel.hytale.server.flock.config.FlockAsset; import com.hypixel.hytale.server.npc.NPCPlugin; import com.hypixel.hytale.server.npc.asset.builder.Builder; import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo; import com.hypixel.hytale.server.npc.entities.NPCEntity; import com.hypixel.hytale.server.npc.pages.EntitySpawnPage; import com.hypixel.hytale.server.npc.role.Role; import com.hypixel.hytale.server.npc.role.RoleDebugFlags; import com.hypixel.hytale.server.spawning.ISpawnableWithModel; import com.hypixel.hytale.server.spawning.SpawnTestResult; import com.hypixel.hytale.server.spawning.SpawningContext; import it.unimi.dsi.fastutil.Pair; import java.util.EnumSet; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class NPCSpawnCommand extends AbstractPlayerCommand { private static final double PLAYER_FOOT_POINT_EPSILON = 0.01; @Nonnull private final RequiredArg roleArg = this.withRequiredArg("role", "server.commands.npc.spawn.role.desc", NPCCommand.NPC_ROLE); @Nonnull private final OptionalArg countArg = this.withOptionalArg("count", "server.commands.npc.spawn.count.desc", ArgTypes.INTEGER) .addValidator(Validators.greaterThan(0)); @Nonnull private final OptionalArg radiusArg = this.withOptionalArg("radius", "server.commands.npc.spawn.radius.desc", ArgTypes.DOUBLE) .addValidator(Validators.greaterThan(0.0)); @Nonnull private final OptionalArg flagsArg = this.withOptionalArg("flags", "server.commands.npc.spawn.flags.desc", ArgTypes.STRING); @Nonnull private final OptionalArg speedArg = this.withOptionalArg("speed", "server.commands.npc.spawn.speed.desc", ArgTypes.DOUBLE) .addValidator(Validators.greaterThan(0.0)); @Nonnull private final FlagArg nonRandomArg = this.withFlagArg("nonrandom", "server.commands.npc.spawn.random.desc"); @Nonnull private final OptionalArg positionSetArg = this.withOptionalArg("position", "server.commands.npc.spawn.position.desc", ArgTypes.STRING); @Nonnull private final OptionalArg posOffsetArg = this.withOptionalArg("posOffset", "server.commands.npc.spawn.posOffset.desc", ArgTypes.STRING); @Nonnull private final OptionalArg headRotationArg = this.withOptionalArg("headRotation", "server.commands.npc.spawn.headRotation.desc", ArgTypes.STRING); @Nonnull private final OptionalArg bodyRotationArg = this.withOptionalArg("bodyRotation", "server.commands.npc.spawn.bodyRotation.desc", ArgTypes.STRING); @Nonnull private final FlagArg randomRotationArg = this.withFlagArg("randomRotation", "server.commands.npc.spawn.randomRotation.desc"); @Nonnull private final FlagArg facingRotationArg = this.withFlagArg("facingRotation", "server.commands.npc.spawn.facingRotation.desc"); @Nonnull private final OptionalArg flockArg = this.withOptionalArg("flock", "server.commands.npc.spawn.flock.desc", ArgTypes.STRING); @Nonnull private final FlagArg testArg = this.withFlagArg("test", "server.commands.npc.spawn.test.desc"); @Nonnull private final FlagArg spawnOnGroundArg = this.withFlagArg("spawnOnGround", "server.commands.npc.spawn.spawnOnGround.desc"); @Nonnull private final FlagArg frozenArg = this.withFlagArg("frozen", "server.commands.npc.spawn.frozen.desc"); @Nonnull private final FlagArg randomModelArg = this.withFlagArg("randomModel", "server.commands.npc.spawn.randomModel.desc"); @Nonnull private final OptionalArg scaleArg = this.withOptionalArg("scale", "server.commands.npc.spawn.scale.desc", ArgTypes.FLOAT); @Nonnull private final FlagArg bypassScaleLimitsArg = this.withFlagArg("bypassScaleLimits", "server.commands.npc.spawn.bypassScaleLimits.desc"); public NPCSpawnCommand() { super("spawn", "server.commands.npc.spawn.desc"); this.addUsageVariant(new NPCSpawnCommand.SpawnPageCommand()); } @Override protected void execute( @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world ) { NPCPlugin npcPlugin = NPCPlugin.get(); BuilderInfo roleInfo = this.roleArg.get(context); int roleIndex = roleInfo.getIndex(); HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType()); assert headRotationComponent != null; Vector3f playerHeadRotation = headRotationComponent.getRotation(); TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType()); assert transformComponent != null; Vector3d playerPosition = transformComponent.getPosition(); BoundingBox boundingBoxComponent = store.getComponent(ref, BoundingBox.getComponentType()); assert boundingBoxComponent != null; Box playerBoundingBox = boundingBoxComponent.getBoundingBox(); int count = this.countArg.provided(context) ? this.countArg.get(context) : 1; double radius = this.radiusArg.provided(context) ? this.radiusArg.get(context) : 8.0; String flagsString = this.flagsArg.provided(context) ? this.flagsArg.get(context) : null; EnumSet flags = flagsString != null ? RoleDebugFlags.getFlags(flagsString.split(",")) : RoleDebugFlags.getPreset("none"); Vector3d velocity = new Vector3d(Vector3d.ZERO); if (this.speedArg.provided(context)) { PhysicsMath.vectorFromAngles(playerHeadRotation.getYaw(), playerHeadRotation.getPitch(), velocity); velocity.setLength(this.speedArg.get(context)); } Random random = (Random)(this.nonRandomArg.get(context) ? new Random(0L) : ThreadLocalRandom.current()); Vector3d posOffset = this.posOffsetArg.provided(context) ? this.parseVector3d(context, this.posOffsetArg.get(context)) : null; Vector3f headRotation = this.headRotationArg.provided(context) ? this.parseVector3f(context, this.headRotationArg.get(context)) : null; boolean randomRotation = false; Vector3f rotation = playerHeadRotation; if (this.bodyRotationArg.provided(context)) { rotation = this.parseVector3f(context, this.bodyRotationArg.get(context)); } else if (this.randomRotationArg.get(context)) { randomRotation = true; } else if (this.facingRotationArg.get(context)) { playerHeadRotation.setY(playerHeadRotation.getY() - (float) Math.PI); } String flockSizeString = this.flockArg.provided(context) ? this.flockArg.get(context) : "1"; Integer flockSize = this.parseFlockSize(context, flockSizeString); if (flockSize != null) { Boolean frozen = this.frozenArg.get(context); npcPlugin.forceValidation(roleIndex); if (!npcPlugin.testAndValidateRole(roleInfo)) { throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.validation_failed")); } else { try { for (int i = 0; i < count; i++) { Builder roleBuilder = npcPlugin.tryGetCachedValidRole(roleIndex); if (roleBuilder == null) { throw new IllegalArgumentException("Can't find a matching role builder"); } if (!(roleBuilder instanceof ISpawnableWithModel spawnable)) { throw new IllegalArgumentException("Role builder must support ISpawnableWithModel interface"); } if (!roleBuilder.isSpawnable()) { throw new IllegalArgumentException("Abstract role templates cannot be spawned directly - a variant needs to be created!"); } SpawningContext spawningContext = new SpawningContext(); if (!spawningContext.setSpawnable(spawnable)) { throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSetRolebuilder")); } TriConsumer, Store> skinApplyingFunction = null; Model model; if (this.randomModelArg.get(context)) { PlayerSkin playerSkin = CosmeticsModule.get().generateRandomSkin(RandomUtil.getSecureRandom()); model = CosmeticsModule.get().createModel(playerSkin); skinApplyingFunction = (npcEntity, entityStoreRef, entityStore) -> { entityStore.putComponent(entityStoreRef, PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(playerSkin)); entityStore.putComponent( entityStoreRef, ApplyRandomSkinPersistedComponent.getComponentType(), ApplyRandomSkinPersistedComponent.INSTANCE ); }; } else { model = spawningContext.getModel(); } if (randomRotation) { rotation = new Vector3f(0.0F, (float)(2.0 * random.nextDouble() * Math.PI), 0.0F); } if (this.scaleArg.provided(context)) { ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(model.getModelAssetId()); assert modelAsset != null; Float scale = this.scaleArg.get(context); if (!this.bypassScaleLimitsArg.get(context)) { scale = MathUtil.clamp(scale, modelAsset.getMinScale(), modelAsset.getMaxScale()); } model = Model.createScaledModel(modelAsset, scale); } Ref npcRef; NPCEntity npc; if (count == 1 && this.testArg.get(context)) { if (!spawningContext.set(world, playerPosition.x, playerPosition.y, playerPosition.z)) { throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotEnoughSpace")); } if (spawnable.canSpawn(spawningContext) != SpawnTestResult.TEST_OK) { throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotSuitable")); } Vector3d spawnPosition = spawningContext.newPosition(); if (posOffset != null) { spawnPosition.add(posOffset); } Pair, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, spawnPosition, rotation, model, skinApplyingFunction); npcRef = (Ref)npcPair.first(); npc = (NPCEntity)npcPair.second(); if (flockSize > 1) { FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, spawnPosition, rotation, flockSize, skinApplyingFunction); } } else { Vector3d position; if (this.positionSetArg.provided(context)) { position = this.parseVector3d(context, this.positionSetArg.get(context)); if (position == null) { return; } position.y = position.y - model.getBoundingBox().min.y; } else { position = new Vector3d(playerPosition); position.y = Math.floor(position.y + playerBoundingBox.min.y + 0.01) - model.getBoundingBox().min.y; } if (posOffset != null) { position.add(posOffset); } Pair, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, position, rotation, model, skinApplyingFunction); npcRef = (Ref)npcPair.first(); npc = (NPCEntity)npcPair.second(); if (flockSize > 1) { FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, position, rotation, flockSize, skinApplyingFunction); } } TransformComponent npcTransformComponent = store.getComponent(npcRef, TransformComponent.getComponentType()); assert npcTransformComponent != null; HeadRotation npcHeadRotationComponent = store.getComponent(npcRef, HeadRotation.getComponentType()); assert npcHeadRotationComponent != null; UUIDComponent npcUuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType()); assert npcUuidComponent != null; if (headRotation != null) { npcHeadRotationComponent.getRotation().assign(headRotation); store.ensureComponent(npcRef, Frozen.getComponentType()); } Vector3d npcPosition = npcTransformComponent.getPosition(); double x = npcPosition.getX(); double y = npcPosition.getY(); double z = npcPosition.getZ(); if (count > 1) { x += random.nextDouble() * 2.0 * radius - radius; z += random.nextDouble() * 2.0 * radius - radius; y += this.spawnOnGroundArg.get(context) ? 0.1 : random.nextDouble() * 2.0 + 5.0; } else { y += 0.1; } npcPosition.assign(x, y, z); npc.saveLeashInformation(npcPosition, npcTransformComponent.getRotation()); if (!velocity.equals(Vector3d.ZERO)) { npc.getRole().forceVelocity(velocity, null, false); } if (frozen) { store.ensureComponent(npcRef, Frozen.getComponentType()); } EnumSet debugFlags = npc.getRoleDebugFlags().clone(); debugFlags.addAll(flags); if (!debugFlags.isEmpty()) { Holder holder = store.removeEntity(npcRef, RemoveReason.UNLOAD); npc.setRoleDebugFlags(debugFlags); store.addEntity(holder, AddReason.LOAD); } NPCPlugin.get() .getLogger() .at(Level.INFO) .log("%s created with id %s at position %s", npc.getRoleName(), npcUuidComponent.getUuid(), Vector3d.formatShortString(npcPosition)); } } catch (IllegalStateException | NullPointerException | IllegalArgumentException var49) { NPCPlugin.get().getLogger().at(Level.WARNING).log("Spawn failed: " + var49.getMessage()); throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.failed").param("reason", var49.getMessage())); } } } } @Nullable private Vector3d parseVector3d(@Nonnull CommandContext context, @Nonnull String str) { String[] parts = str.split(","); if (parts.length != 3) { context.sendMessage(Message.raw("Invalid Vector3d format: must be three comma-separated doubles")); return null; } else { try { return new Vector3d(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), Double.parseDouble(parts[2])); } catch (NumberFormatException var5) { context.sendMessage(Message.raw("Invalid Vector3d format: " + var5.getMessage())); return null; } } } @Nullable private Vector3f parseVector3f(@Nonnull CommandContext context, @Nonnull String str) { String[] parts = str.split(","); if (parts.length != 3) { context.sendMessage(Message.raw("Invalid Vector3f format: must be three comma-separated floats")); return null; } else { try { return new Vector3f(Float.parseFloat(parts[0]), Float.parseFloat(parts[1]), Float.parseFloat(parts[2])); } catch (NumberFormatException var5) { context.sendMessage(Message.raw("Invalid Vector3f format: " + var5.getMessage())); return null; } } } @Nullable private Integer parseFlockSize(@Nonnull CommandContext context, @Nonnull String str) { try { Integer size = Integer.valueOf(str); if (size <= 0) { context.sendMessage(Message.raw("Flock size must be greater than 0!")); return null; } else { return size; } } catch (NumberFormatException var5) { FlockAsset flockDefinition = FlockAsset.getAssetMap().getAsset(str); if (flockDefinition == null) { context.sendMessage(Message.raw("No such flock asset: " + str)); return null; } else { return flockDefinition.pickFlockSize(); } } } public static class SpawnPageCommand extends AbstractPlayerCommand { public SpawnPageCommand() { super("server.commands.npc.spawn.page.desc"); } @Override protected void execute( @Nonnull CommandContext context, @Nonnull Store store, @Nonnull Ref ref, @Nonnull PlayerRef playerRef, @Nonnull World world ) { Player playerComponent = store.getComponent(ref, Player.getComponentType()); assert playerComponent != null; PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType()); assert playerRefComponent != null; playerComponent.getPageManager().openCustomPage(ref, store, new EntitySpawnPage(playerRefComponent)); } } }