255 lines
9.5 KiB
Java
255 lines
9.5 KiB
Java
package com.hypixel.hytale.server.spawning.controllers;
|
|
|
|
import com.hypixel.hytale.component.ComponentAccessor;
|
|
import com.hypixel.hytale.component.Ref;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.server.core.entity.UUIDComponent;
|
|
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.npc.NPCPlugin;
|
|
import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn;
|
|
import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters;
|
|
import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity;
|
|
import com.hypixel.hytale.server.spawning.jobs.NPCBeaconSpawnJob;
|
|
import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper;
|
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
|
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.time.Duration;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.logging.Level;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class BeaconSpawnController extends SpawnController<NPCBeaconSpawnJob> {
|
|
@Nonnull
|
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
public static final int MAX_ATTEMPTS_PER_TICK = 5;
|
|
public static final double ROUNDING_BREAK_POINT = 0.25;
|
|
@Nonnull
|
|
private final Ref<EntityStore> ownerRef;
|
|
private final List<Ref<EntityStore>> spawnedEntities = new ObjectArrayList();
|
|
private final List<PlayerRef> playersInRegion = new ObjectArrayList();
|
|
private int nextPlayerIndex = 0;
|
|
private final Object2IntMap<UUID> entitiesPerPlayer = new Object2IntOpenHashMap();
|
|
private final Object2DoubleMap<Ref<EntityStore>> entityTimeoutCounter = new Object2DoubleOpenHashMap();
|
|
private final IntSet unspawnableRoles = new IntOpenHashSet();
|
|
private final Comparator<PlayerRef> threatComparator = Comparator.comparingInt(playerRef -> this.entitiesPerPlayer.getOrDefault(playerRef.getUuid(), 0));
|
|
private int baseMaxTotalSpawns;
|
|
private int currentScaledMaxTotalSpawns;
|
|
private int[] baseMaxConcurrentSpawns;
|
|
private int currentScaledMaxConcurrentSpawns;
|
|
private int spawnsThisRound;
|
|
private int remainingSpawns;
|
|
private boolean roundStart = true;
|
|
private double beaconRadiusSquared;
|
|
private double spawnRadiusSquared;
|
|
private double despawnNPCAfterTimeout;
|
|
private Duration despawnBeaconAfterTimeout;
|
|
private boolean despawnNPCsIfIdle;
|
|
|
|
public BeaconSpawnController(@Nonnull World world, @Nonnull Ref<EntityStore> ownerRef) {
|
|
super(world);
|
|
this.ownerRef = ownerRef;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxActiveJobs() {
|
|
return Math.min(this.remainingSpawns, this.baseMaxActiveJobs);
|
|
}
|
|
|
|
@Nullable
|
|
public NPCBeaconSpawnJob createRandomSpawnJob(@Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
LegacySpawnBeaconEntity legacySpawnBeaconComponent = componentAccessor.getComponent(this.ownerRef, LegacySpawnBeaconEntity.getComponentType());
|
|
|
|
assert legacySpawnBeaconComponent != null;
|
|
|
|
BeaconSpawnWrapper wrapper = legacySpawnBeaconComponent.getSpawnWrapper();
|
|
RoleSpawnParameters spawn = wrapper.pickRole(ThreadLocalRandom.current());
|
|
String spawnId = spawn.getId();
|
|
int roleIndex = NPCPlugin.get().getIndex(spawnId);
|
|
if (roleIndex >= 0 && !this.unspawnableRoles.contains(roleIndex)) {
|
|
NPCBeaconSpawnJob job = null;
|
|
int predictedTotal = this.spawnedEntities.size() + this.activeJobs.size();
|
|
if (this.activeJobs.size() < this.getMaxActiveJobs()
|
|
&& this.nextPlayerIndex < this.playersInRegion.size()
|
|
&& predictedTotal < this.currentScaledMaxTotalSpawns) {
|
|
job = this.idleJobs.isEmpty() ? new NPCBeaconSpawnJob() : this.idleJobs.pop();
|
|
job.beginProbing(this.playersInRegion.get(this.nextPlayerIndex++), this.currentScaledMaxConcurrentSpawns, roleIndex, spawn.getFlockDefinition());
|
|
this.activeJobs.add(job);
|
|
if (this.nextPlayerIndex >= this.playersInRegion.size()) {
|
|
this.nextPlayerIndex = 0;
|
|
}
|
|
}
|
|
|
|
return job;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void initialise(@Nonnull BeaconSpawnWrapper spawnWrapper) {
|
|
BeaconNPCSpawn spawn = spawnWrapper.getSpawn();
|
|
this.baseMaxTotalSpawns = spawn.getMaxSpawnedNpcs();
|
|
this.baseMaxConcurrentSpawns = spawn.getConcurrentSpawnsRange();
|
|
double beaconRadius = spawn.getBeaconRadius();
|
|
this.beaconRadiusSquared = beaconRadius * beaconRadius;
|
|
double spawnRadius = spawn.getSpawnRadius();
|
|
this.spawnRadiusSquared = spawnRadius * spawnRadius;
|
|
this.despawnNPCAfterTimeout = spawn.getNpcIdleDespawnTimeSeconds();
|
|
this.despawnBeaconAfterTimeout = spawn.getBeaconVacantDespawnTime();
|
|
this.despawnNPCsIfIdle = spawn.getNpcSpawnState() != null;
|
|
}
|
|
|
|
public int getSpawnsThisRound() {
|
|
return this.spawnsThisRound;
|
|
}
|
|
|
|
public void setRemainingSpawns(int remainingSpawns) {
|
|
this.remainingSpawns = remainingSpawns;
|
|
}
|
|
|
|
public void addRoundSpawn() {
|
|
this.spawnsThisRound++;
|
|
this.remainingSpawns--;
|
|
}
|
|
|
|
public boolean isRoundStart() {
|
|
return this.roundStart;
|
|
}
|
|
|
|
public void setRoundStart(boolean roundStart) {
|
|
this.roundStart = roundStart;
|
|
}
|
|
|
|
public Ref<EntityStore> getOwnerRef() {
|
|
return this.ownerRef;
|
|
}
|
|
|
|
public int[] getBaseMaxConcurrentSpawns() {
|
|
return this.baseMaxConcurrentSpawns;
|
|
}
|
|
|
|
public List<PlayerRef> getPlayersInRegion() {
|
|
return this.playersInRegion;
|
|
}
|
|
|
|
public int getCurrentScaledMaxConcurrentSpawns() {
|
|
return this.currentScaledMaxConcurrentSpawns;
|
|
}
|
|
|
|
public void setCurrentScaledMaxConcurrentSpawns(int currentScaledMaxConcurrentSpawns) {
|
|
this.currentScaledMaxConcurrentSpawns = currentScaledMaxConcurrentSpawns;
|
|
}
|
|
|
|
public Duration getDespawnBeaconAfterTimeout() {
|
|
return this.despawnBeaconAfterTimeout;
|
|
}
|
|
|
|
public double getSpawnRadiusSquared() {
|
|
return this.spawnRadiusSquared;
|
|
}
|
|
|
|
public double getBeaconRadiusSquared() {
|
|
return this.beaconRadiusSquared;
|
|
}
|
|
|
|
public int getBaseMaxTotalSpawns() {
|
|
return this.baseMaxTotalSpawns;
|
|
}
|
|
|
|
public void setCurrentScaledMaxTotalSpawns(int currentScaledMaxTotalSpawns) {
|
|
this.currentScaledMaxTotalSpawns = currentScaledMaxTotalSpawns;
|
|
}
|
|
|
|
public List<Ref<EntityStore>> getSpawnedEntities() {
|
|
return this.spawnedEntities;
|
|
}
|
|
|
|
public void setNextPlayerIndex(int nextPlayerIndex) {
|
|
this.nextPlayerIndex = nextPlayerIndex;
|
|
}
|
|
|
|
public Object2DoubleMap<Ref<EntityStore>> getEntityTimeoutCounter() {
|
|
return this.entityTimeoutCounter;
|
|
}
|
|
|
|
public Object2IntMap<UUID> getEntitiesPerPlayer() {
|
|
return this.entitiesPerPlayer;
|
|
}
|
|
|
|
public boolean isDespawnNPCsIfIdle() {
|
|
return this.despawnNPCsIfIdle;
|
|
}
|
|
|
|
public double getDespawnNPCAfterTimeout() {
|
|
return this.despawnNPCAfterTimeout;
|
|
}
|
|
|
|
public Comparator<PlayerRef> getThreatComparator() {
|
|
return this.threatComparator;
|
|
}
|
|
|
|
public void notifySpawnedEntityExists(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
this.spawnedEntities.add(ref);
|
|
HytaleLogger.Api context = LOGGER.at(Level.FINE);
|
|
if (context.isEnabled()) {
|
|
UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType());
|
|
|
|
assert ownerUuidComponent != null;
|
|
|
|
context.log("Registering NPC with reference %s with Spawn Beacon %s", ref, ownerUuidComponent.getUuid());
|
|
}
|
|
}
|
|
|
|
public void onJobFinished(@Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
if (++this.spawnsThisRound >= this.currentScaledMaxConcurrentSpawns) {
|
|
this.onAllConcurrentSpawned(componentAccessor);
|
|
}
|
|
}
|
|
|
|
public void notifyNPCRemoval(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
this.spawnedEntities.remove(ref);
|
|
this.entityTimeoutCounter.removeDouble(ref);
|
|
if (this.spawnedEntities.size() == this.currentScaledMaxTotalSpawns - 1) {
|
|
LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor);
|
|
}
|
|
|
|
HytaleLogger.Api context = LOGGER.at(Level.FINE);
|
|
if (context.isEnabled()) {
|
|
UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType());
|
|
|
|
assert ownerUuidComponent != null;
|
|
|
|
context.log("Removing NPC with reference %s from Spawn Beacon %s", ref, ownerUuidComponent.getUuid());
|
|
}
|
|
}
|
|
|
|
public boolean hasSlots() {
|
|
return this.spawnedEntities.size() < this.currentScaledMaxTotalSpawns;
|
|
}
|
|
|
|
public void markNPCUnspawnable(int roleIndex) {
|
|
this.unspawnableRoles.add(roleIndex);
|
|
}
|
|
|
|
public void clearUnspawnableNPCs() {
|
|
this.unspawnableRoles.clear();
|
|
}
|
|
|
|
public void onAllConcurrentSpawned(@Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
this.spawnsThisRound = 0;
|
|
this.remainingSpawns = 0;
|
|
LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor);
|
|
this.roundStart = true;
|
|
}
|
|
}
|