hytale-server/com/hypixel/hytale/server/spawning/assets/spawns/config/NPCSpawn.java

318 lines
13 KiB
Java

package com.hypixel.hytale.server.spawning.assets.spawns.config;
import com.hypixel.hytale.assetstore.AssetExtraInfo;
import com.hypixel.hytale.assetstore.codec.AssetBuilderCodec;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
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.array.ArrayCodec;
import com.hypixel.hytale.codec.codecs.map.EnumMapCodec;
import com.hypixel.hytale.codec.schema.metadata.ui.UIPropertyTitle;
import com.hypixel.hytale.codec.validation.ValidationResults;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import com.hypixel.hytale.server.spawning.assets.spawns.LightType;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public abstract class NPCSpawn {
public static final float HOURS_PER_DAY = 24.0F;
public static final BuilderCodec<NPCSpawn> BASE_CODEC = AssetBuilderCodec.abstractBuilder(NPCSpawn.class)
.documentation("A specification for spawning NPCs, including spawn and despawn parameters.")
.<RoleSpawnParameters[]>appendInherited(
new KeyedCodec<>("NPCs", new ArrayCodec<>(RoleSpawnParameters.CODEC, RoleSpawnParameters[]::new)),
(spawn, o) -> spawn.npcs = o,
spawn -> spawn.npcs,
(spawn, parent) -> spawn.npcs = parent.npcs
)
.documentation("A required list of **Role Spawn Parameters** defining each NPC that can be spawned and their relative weights.")
.metadata(new UIPropertyTitle("NPCs"))
.addValidator(Validators.nonNull())
.addValidator(Validators.nonEmptyArray())
.add()
.<NPCSpawn.DespawnParameters>appendInherited(
new KeyedCodec<>("Despawn", NPCSpawn.DespawnParameters.CODEC),
(spawn, o) -> spawn.despawnParameters = o,
spawn -> spawn.despawnParameters,
(spawn, parent) -> spawn.despawnParameters = parent.despawnParameters
)
.documentation("Optional **Despawn Parameters** to control NPC despawning.")
.add()
.<double[]>appendInherited(new KeyedCodec<>("DayTimeRange", Codec.DOUBLE_ARRAY), (spawn, o) -> {
spawn.dayTimeRange = o;
spawn.dayTimeRange[0] = spawn.dayTimeRange[0] / 24.0;
spawn.dayTimeRange[1] = spawn.dayTimeRange[1] / 24.0;
}, spawn -> new double[]{spawn.dayTimeRange[0] * 24.0, spawn.dayTimeRange[1] * 24.0}, (spawn, parent) -> spawn.dayTimeRange = parent.dayTimeRange)
.documentation("An optional hour range within which the NPCs/beacon will spawn (between 0 and 24).")
.addValidator(Validators.doubleArraySize(2))
.add()
.<int[]>appendInherited(
new KeyedCodec<>("MoonPhaseRange", Codec.INT_ARRAY),
(spawn, o) -> spawn.moonPhaseRange = o,
spawn -> new int[]{spawn.moonPhaseRange[0], spawn.moonPhaseRange[1]},
(spawn, parent) -> spawn.moonPhaseRange = parent.moonPhaseRange
)
.documentation("An optional moon phase range during which the NPCs/beacon will spawn (must be greater than or equal to 0).")
.addValidator(Validators.intArraySize(2))
.add()
.<Map<LightType, double[]>>appendInherited(
new KeyedCodec<>(
"LightRanges",
new EnumMapCodec<>(LightType.class, Codec.DOUBLE_ARRAY)
.documentKey(LightType.Light, "Total light level.")
.documentKey(
LightType.SkyLight,
"Light level based on how deep under cover the position is relative to the open sky (e.g. inside a cave will be low SkyLight)."
)
.documentKey(LightType.Sunlight, "Light level based on time of day (peaks at around noon and is 0 during most of the night).")
.documentKey(LightType.RedLight, "Red light level.")
.documentKey(LightType.GreenLight, "Green light level.")
.documentKey(LightType.BlueLight, "Blue light level.")
),
(spawn, o) -> spawn.lightTypeMap = o,
spawn -> spawn.lightTypeMap,
(spawn, parent) -> spawn.lightTypeMap = parent.lightTypeMap
)
.documentation("Optional light ranges to spawn the NPCs/beacon in, defined between 0 and 100.")
.add()
.<Boolean>appendInherited(
new KeyedCodec<>("ScaleDayTimeRange", Codec.BOOLEAN),
(spawn, b) -> spawn.scaleDayTimeRange = b,
spawn -> spawn.scaleDayTimeRange,
(spawn, parent) -> spawn.scaleDayTimeRange = parent.scaleDayTimeRange
)
.documentation(
"If set to true, instead of using absolute hour values for DayTimeRange, it will be scaled based on the world's DaytimePortion.\n\n * 0 and 24 will represent the middle of the night portion.\n * 6 will represent the moment of sunrise.\n * 12 will represent the middle of the day portion.\n * 18 will represent the moment of sunset."
)
.add()
.afterDecode(spawn -> {
if (spawn.environments != null && spawn.environments.length != 0) {
IntOpenHashSet environmentSet = new IntOpenHashSet();
IndexedLookupTableAssetMap<String, Environment> environments = Environment.getAssetMap();
for (String environment : spawn.environments) {
int index = environments.getIndex(environment);
if (index != Integer.MIN_VALUE) {
environmentSet.add(index);
}
}
environmentSet.trim();
spawn.environmentIds = IntSets.unmodifiable(environmentSet);
} else {
spawn.environmentIds = IntSets.EMPTY_SET;
}
})
.validator((asset, results) -> {
double[] dayTimeRange = asset.getDayTimeRange();
if (dayTimeRange[0] < 0.0 || dayTimeRange[1] < 0.0) {
results.fail("DayTimeRange values must be >=0");
}
int[] moonPhaseRange = asset.getMoonPhaseRange();
if (moonPhaseRange[0] < 0 || moonPhaseRange[1] < 0) {
results.fail("MoonPhaseRange values must be >=0");
}
for (LightType lightType : LightType.VALUES) {
validateLightRange(results, lightType.name(), asset.getLightRange(lightType));
}
NPCSpawn.DespawnParameters despawnParameters = asset.getDespawnParameters();
if (despawnParameters != null) {
dayTimeRange = despawnParameters.getDayTimeRange();
if (dayTimeRange[0] < 0.0 || dayTimeRange[1] < 0.0) {
results.fail("Despawn DayTimeRange values must be >=0");
}
moonPhaseRange = despawnParameters.getMoonPhaseRange();
if (moonPhaseRange[0] < 0 || moonPhaseRange[1] < 0) {
results.fail("Despawn MoonPhaseRange values must be >=0");
}
}
})
.build();
public static final double[] DEFAULT_DAY_TIME_RANGE = new double[]{0.0, Double.MAX_VALUE};
public static final int[] DEFAULT_MOON_PHASE_RANGE = new int[]{0, Integer.MAX_VALUE};
public static final double[] FULL_LIGHT_RANGE = new double[]{0.0, 100.0};
protected AssetExtraInfo.Data data;
protected String id;
protected RoleSpawnParameters[] npcs;
protected NPCSpawn.DespawnParameters despawnParameters;
protected String[] environments;
protected IntSet environmentIds = IntSets.EMPTY_SET;
protected double[] dayTimeRange = DEFAULT_DAY_TIME_RANGE;
protected int[] moonPhaseRange = DEFAULT_MOON_PHASE_RANGE;
protected Map<LightType, double[]> lightTypeMap;
protected boolean scaleDayTimeRange = true;
private static void validateLightRange(@Nonnull ValidationResults results, String parameter, @Nonnull double[] lightRange) {
for (int i = 0; i < lightRange.length; i++) {
double value = lightRange[i];
if (value < 0.0 || value > 100.0) {
results.fail(String.format("%s must be between 0 and 100 (inclusive)", parameter));
}
if (i > 0 && value < lightRange[i - 1]) {
results.fail(String.format("Values in %s must be weakly monotonic", parameter));
}
}
}
public NPCSpawn(
String id,
RoleSpawnParameters[] npcs,
NPCSpawn.DespawnParameters despawnParameters,
String[] environments,
IntSet environmentIds,
double[] dayTimeRange,
int[] moonPhaseRange,
Map<LightType, double[]> lightTypeMap,
boolean scaleDayTimeRange
) {
this.id = id;
this.npcs = npcs;
this.despawnParameters = despawnParameters;
this.environments = environments;
this.environmentIds = environmentIds;
this.dayTimeRange = dayTimeRange;
this.moonPhaseRange = moonPhaseRange;
this.lightTypeMap = lightTypeMap;
this.scaleDayTimeRange = scaleDayTimeRange;
}
protected NPCSpawn(String id) {
this.id = id;
}
protected NPCSpawn() {
}
public abstract String getId();
public RoleSpawnParameters[] getNPCs() {
return this.npcs;
}
public NPCSpawn.DespawnParameters getDespawnParameters() {
return this.despawnParameters;
}
public String[] getEnvironments() {
return this.environments;
}
public IntSet getEnvironmentIds() {
return this.environmentIds;
}
public double[] getDayTimeRange() {
return this.dayTimeRange;
}
public int[] getMoonPhaseRange() {
return this.moonPhaseRange;
}
public double[] getLightRange(LightType lightType) {
if (this.lightTypeMap != null && !this.lightTypeMap.isEmpty()) {
double[] array = this.lightTypeMap.get(lightType);
if (array != null) {
return array;
}
}
return FULL_LIGHT_RANGE;
}
public boolean isScaleDayTimeRange() {
return this.scaleDayTimeRange;
}
@Nonnull
@Override
public String toString() {
return "NPCSpawn{id='"
+ this.id
+ "', npcs="
+ Arrays.deepToString(this.npcs)
+ ", despawnParameters="
+ (this.despawnParameters != null ? this.despawnParameters.toString() : "Null")
+ ", environments="
+ Arrays.toString((Object[])this.environments)
+ ", dayTimeRange="
+ Arrays.toString(this.dayTimeRange)
+ ", moonPhaseRange="
+ Arrays.toString(this.moonPhaseRange)
+ ", lightTypeMap="
+ (
this.lightTypeMap != null
? this.lightTypeMap
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + Arrays.toString(entry.getValue()))
.collect(Collectors.joining(", ", "{", "}"))
: "Null"
)
+ ", scaleDayTimeRange="
+ this.scaleDayTimeRange
+ "}";
}
public static class DespawnParameters {
public static final BuilderCodec<NPCSpawn.DespawnParameters> CODEC = BuilderCodec.builder(
NPCSpawn.DespawnParameters.class, NPCSpawn.DespawnParameters::new
)
.documentation("A set of parameters that determine if NPCs should despawn.")
.<double[]>append(new KeyedCodec<>("DayTimeRange", Codec.DOUBLE_ARRAY), (parameters, o) -> {
parameters.dayTimeRange = o;
parameters.dayTimeRange[0] = parameters.dayTimeRange[0] / 24.0;
parameters.dayTimeRange[1] = parameters.dayTimeRange[1] / 24.0;
}, parameters -> new double[]{parameters.dayTimeRange[0] * 24.0, parameters.dayTimeRange[1] * 24.0})
.documentation("An optional hour range within which the NPCs will despawn (between 0 and 24). For Spawn Beacons, this refers to the beacon itself.")
.addValidator(Validators.doubleArraySize(2))
.add()
.<int[]>append(
new KeyedCodec<>("MoonPhaseRange", Codec.INT_ARRAY),
(parameters, o) -> parameters.moonPhaseRange = o,
parameters -> new int[]{parameters.moonPhaseRange[0], parameters.moonPhaseRange[1]}
)
.documentation(
"An optional moon phase range during which the NPCs will despawn (must be greater than or equal to 0). For Spawn Beacons, this refers to the beacon itself."
)
.addValidator(Validators.intArraySize(2))
.add()
.build();
protected double[] dayTimeRange = NPCSpawn.DEFAULT_DAY_TIME_RANGE;
protected int[] moonPhaseRange = NPCSpawn.DEFAULT_MOON_PHASE_RANGE;
public DespawnParameters(double[] dayTimeRange, int[] moonPhaseRange) {
this.dayTimeRange = dayTimeRange;
this.moonPhaseRange = moonPhaseRange;
}
protected DespawnParameters() {
}
public double[] getDayTimeRange() {
return this.dayTimeRange;
}
public int[] getMoonPhaseRange() {
return this.moonPhaseRange;
}
@Nonnull
@Override
public String toString() {
return "DespawnParameters{dayTimeRange=" + Arrays.toString(this.dayTimeRange) + ", moonPhaseRange=" + Arrays.toString(this.moonPhaseRange) + "}";
}
}
}