315 lines
15 KiB
Java
315 lines
15 KiB
Java
package com.hypixel.hytale.server.flock;
|
|
|
|
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
|
|
import com.hypixel.hytale.assetstore.AssetRegistry;
|
|
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
|
|
import com.hypixel.hytale.component.AddReason;
|
|
import com.hypixel.hytale.component.CommandBuffer;
|
|
import com.hypixel.hytale.component.ComponentAccessor;
|
|
import com.hypixel.hytale.component.ComponentRegistryProxy;
|
|
import com.hypixel.hytale.component.ComponentType;
|
|
import com.hypixel.hytale.component.Holder;
|
|
import com.hypixel.hytale.component.Ref;
|
|
import com.hypixel.hytale.component.Store;
|
|
import com.hypixel.hytale.component.system.WorldEventSystem;
|
|
import com.hypixel.hytale.function.consumer.TriConsumer;
|
|
import com.hypixel.hytale.math.random.RandomExtra;
|
|
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.server.core.asset.HytaleAssetStore;
|
|
import com.hypixel.hytale.server.core.entity.UUIDComponent;
|
|
import com.hypixel.hytale.server.core.entity.group.EntityGroup;
|
|
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.plugin.JavaPlugin;
|
|
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
|
|
import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent;
|
|
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
|
|
import com.hypixel.hytale.server.flock.config.FlockAsset;
|
|
import com.hypixel.hytale.server.flock.config.RangeSizeFlockAsset;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockBeacon;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockJoin;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockLeave;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockSetTarget;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockState;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderBodyMotionFlock;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderEntityFilterFlock;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockCombatDamage;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockLeader;
|
|
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorInflictedDamage;
|
|
import com.hypixel.hytale.server.flock.decisionmaker.conditions.FlockSizeCondition;
|
|
import com.hypixel.hytale.server.npc.NPCPlugin;
|
|
import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition;
|
|
import com.hypixel.hytale.server.npc.entities.NPCEntity;
|
|
import com.hypixel.hytale.server.npc.role.Role;
|
|
import com.hypixel.hytale.server.npc.systems.PositionCacheSystems;
|
|
import it.unimi.dsi.fastutil.Pair;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class FlockPlugin extends JavaPlugin {
|
|
private static FlockPlugin instance;
|
|
private final Int2ObjectConcurrentHashMap<Map<UUID, UUID>> prefabFlockRemappings = new Int2ObjectConcurrentHashMap<>();
|
|
private ComponentType<EntityStore, Flock> flockComponentType;
|
|
private ComponentType<EntityStore, FlockMembership> flockMembershipComponentType;
|
|
private ComponentType<EntityStore, PersistentFlockData> persistentFlockDataComponentType;
|
|
|
|
public static FlockPlugin get() {
|
|
return instance;
|
|
}
|
|
|
|
public FlockPlugin(@Nonnull JavaPluginInit init) {
|
|
super(init);
|
|
}
|
|
|
|
@Override
|
|
public void setup() {
|
|
instance = this;
|
|
ComponentRegistryProxy<EntityStore> entityStoreRegistry = this.getEntityStoreRegistry();
|
|
AssetRegistry.register(
|
|
((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(
|
|
FlockAsset.class, new IndexedLookupTableAssetMap<>(FlockAsset[]::new)
|
|
)
|
|
.setPath("NPC/Flocks"))
|
|
.setCodec(FlockAsset.CODEC))
|
|
.setKeyFunction(FlockAsset::getId))
|
|
.setReplaceOnRemove(RangeSizeFlockAsset::getUnknownFor))
|
|
.build()
|
|
);
|
|
this.getEntityStoreRegistry().registerSystem(new FlockPlugin.PrefabPasteEventSystem(this));
|
|
NPCPlugin.get()
|
|
.registerCoreComponentType("Flock", BuilderBodyMotionFlock::new)
|
|
.registerCoreComponentType("JoinFlock", BuilderActionFlockJoin::new)
|
|
.registerCoreComponentType("LeaveFlock", BuilderActionFlockLeave::new)
|
|
.registerCoreComponentType("FlockState", BuilderActionFlockState::new)
|
|
.registerCoreComponentType("FlockTarget", BuilderActionFlockSetTarget::new)
|
|
.registerCoreComponentType("FlockBeacon", BuilderActionFlockBeacon::new)
|
|
.registerCoreComponentType("Flock", BuilderEntityFilterFlock::new)
|
|
.registerCoreComponentType("FlockCombatDamage", BuilderSensorFlockCombatDamage::new)
|
|
.registerCoreComponentType("InflictedDamage", BuilderSensorInflictedDamage::new)
|
|
.registerCoreComponentType("FlockLeader", BuilderSensorFlockLeader::new);
|
|
Condition.CODEC.register("FlockSize", FlockSizeCondition.class, FlockSizeCondition.CODEC);
|
|
this.flockComponentType = entityStoreRegistry.registerComponent(Flock.class, () -> {
|
|
throw new UnsupportedOperationException("Not implemented");
|
|
});
|
|
entityStoreRegistry.registerSystem(new FlockSystems.EntityRemoved(this.flockComponentType));
|
|
entityStoreRegistry.registerSystem(new FlockSystems.Ticking(this.flockComponentType));
|
|
entityStoreRegistry.registerSystem(new FlockSystems.PlayerChangeGameModeEventSystem());
|
|
this.flockMembershipComponentType = entityStoreRegistry.registerComponent(FlockMembership.class, "FlockMembership", FlockMembership.CODEC);
|
|
this.persistentFlockDataComponentType = entityStoreRegistry.registerComponent(PersistentFlockData.class, "FlockData", PersistentFlockData.CODEC);
|
|
entityStoreRegistry.registerSystem(new FlockMembershipSystems.EntityRef(this.flockMembershipComponentType));
|
|
entityStoreRegistry.registerSystem(new FlockMembershipSystems.RefChange(this.flockMembershipComponentType));
|
|
entityStoreRegistry.registerSystem(new PositionCacheSystems.OnFlockJoinSystem(NPCEntity.getComponentType(), this.flockMembershipComponentType));
|
|
entityStoreRegistry.registerSystem(new FlockDeathSystems.EntityDeath());
|
|
entityStoreRegistry.registerSystem(new FlockDeathSystems.PlayerDeath());
|
|
entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageReceived());
|
|
entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageDealt());
|
|
entityStoreRegistry.registerSystem(new FlockMembershipSystems.NPCAddedFromWorldGen());
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
}
|
|
|
|
@Override
|
|
public void shutdown() {
|
|
}
|
|
|
|
public ComponentType<EntityStore, Flock> getFlockComponentType() {
|
|
return this.flockComponentType;
|
|
}
|
|
|
|
public ComponentType<EntityStore, FlockMembership> getFlockMembershipComponentType() {
|
|
return this.flockMembershipComponentType;
|
|
}
|
|
|
|
public ComponentType<EntityStore, PersistentFlockData> getPersistentFlockDataComponentType() {
|
|
return this.persistentFlockDataComponentType;
|
|
}
|
|
|
|
@Nonnull
|
|
public UUID getPrefabRemappedFlockReference(int prefabId, UUID oldId) {
|
|
return this.prefabFlockRemappings.get(prefabId).computeIfAbsent(oldId, s -> UUID.randomUUID());
|
|
}
|
|
|
|
@Nullable
|
|
public static Ref<EntityStore> trySpawnFlock(
|
|
@Nonnull Ref<EntityStore> npcRef,
|
|
@Nonnull NPCEntity npc,
|
|
@Nonnull Store<EntityStore> store,
|
|
int roleIndex,
|
|
@Nonnull Vector3d position,
|
|
Vector3f rotation,
|
|
@Nullable FlockAsset flockDefinition,
|
|
TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn
|
|
) {
|
|
int flockSize = flockDefinition != null ? flockDefinition.pickFlockSize() : 1;
|
|
return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, flockDefinition, null, postSpawn, store);
|
|
}
|
|
|
|
@Nullable
|
|
public static Ref<EntityStore> trySpawnFlock(
|
|
@Nonnull Ref<EntityStore> npcRef,
|
|
@Nonnull NPCEntity npc,
|
|
@Nonnull Store<EntityStore> store,
|
|
int roleIndex,
|
|
@Nonnull Vector3d position,
|
|
Vector3f rotation,
|
|
int flockSize,
|
|
TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn
|
|
) {
|
|
return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, null, null, postSpawn, store);
|
|
}
|
|
|
|
@Nullable
|
|
public static Ref<EntityStore> trySpawnFlock(
|
|
@Nonnull Ref<EntityStore> npcRef,
|
|
@Nonnull NPCEntity npc,
|
|
int roleIndex,
|
|
@Nonnull Vector3d position,
|
|
Vector3f rotation,
|
|
int flockSize,
|
|
FlockAsset flockDefinition,
|
|
TriConsumer<NPCEntity, Holder<EntityStore>, Store<EntityStore>> preAddToWorld,
|
|
TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn,
|
|
@Nonnull Store<EntityStore> store
|
|
) {
|
|
if (flockSize > 1 && npcRef.isValid()) {
|
|
Role role = npc.getRole();
|
|
|
|
assert role != null;
|
|
|
|
FlockMembership membershipComponent = store.getComponent(npcRef, FlockMembership.getComponentType());
|
|
Ref<EntityStore> flockReference = membershipComponent != null
|
|
? membershipComponent.getFlockRef()
|
|
: createFlock(store, flockDefinition, role.getFlockAllowedRoles());
|
|
if (membershipComponent == null) {
|
|
FlockMembershipSystems.join(npcRef, flockReference, store);
|
|
}
|
|
|
|
BoundingBox boundingBoxComponent = store.getComponent(npcRef, BoundingBox.getComponentType());
|
|
|
|
assert boundingBoxComponent != null;
|
|
|
|
TransformComponent transformComponent = store.getComponent(npcRef, TransformComponent.getComponentType());
|
|
|
|
assert transformComponent != null;
|
|
|
|
Box boundingBox = boundingBoxComponent.getBoundingBox();
|
|
Vector3f bodyRotation = transformComponent.getRotation();
|
|
double x = position.getX();
|
|
int y = MathUtil.floor(position.getY() + boundingBox.min.y + 1.0E-6);
|
|
double z = position.getZ();
|
|
double yaw = bodyRotation.getYaw();
|
|
boolean randomSpawn = role.isFlockSpawnTypesRandom();
|
|
int[] roles = role.getFlockSpawnTypes();
|
|
int rolesSize = roles == null ? 0 : roles.length;
|
|
int index = 0;
|
|
int memberRoleIndex = roleIndex;
|
|
|
|
for (int i = 1; i < flockSize; i++) {
|
|
if (rolesSize > 0) {
|
|
if (randomSpawn) {
|
|
memberRoleIndex = roles[RandomExtra.randomRange(rolesSize)];
|
|
} else {
|
|
memberRoleIndex = roles[index];
|
|
index = (index + 1) % rolesSize;
|
|
}
|
|
}
|
|
|
|
Pair<Ref<EntityStore>, NPCEntity> memberPair = NPCPlugin.get()
|
|
.spawnEntity(store, memberRoleIndex, position, rotation, null, preAddToWorld, postSpawn);
|
|
Ref<EntityStore> memberRef = (Ref<EntityStore>)memberPair.first();
|
|
if (memberRef != null && memberRef.isValid()) {
|
|
BoundingBox memberBoundingBoxComponent = store.getComponent(memberRef, BoundingBox.getComponentType());
|
|
|
|
assert memberBoundingBoxComponent != null;
|
|
|
|
TransformComponent memberTransformComponent = store.getComponent(memberRef, TransformComponent.getComponentType());
|
|
|
|
assert memberTransformComponent != null;
|
|
|
|
HeadRotation memberHeadRotationComponent = store.getComponent(memberRef, HeadRotation.getComponentType());
|
|
|
|
assert memberHeadRotationComponent != null;
|
|
|
|
double offsetY = y - memberBoundingBoxComponent.getBoundingBox().min.y;
|
|
memberTransformComponent.getRotation().setYaw((float)(yaw + RandomExtra.randomRange((float) (Math.PI / 4), (float) (Math.PI / 4))));
|
|
memberHeadRotationComponent.getRotation().setPitch(0.0F);
|
|
memberTransformComponent.getPosition().assign(x + RandomExtra.randomRange(-0.5, 0.5), offsetY, z + RandomExtra.randomRange(-0.5, 0.5));
|
|
FlockMembershipSystems.join(memberRef, flockReference, store);
|
|
}
|
|
}
|
|
|
|
return flockReference;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Deprecated
|
|
public static Flock getFlock(@Nonnull ComponentAccessor<EntityStore> componentAccessor, @Nonnull Ref<EntityStore> reference) {
|
|
FlockMembership flockMembershipComponent = componentAccessor.getComponent(reference, FlockMembership.getComponentType());
|
|
if (flockMembershipComponent == null) {
|
|
return null;
|
|
} else {
|
|
Ref<EntityStore> membershipRef = flockMembershipComponent.getFlockRef();
|
|
return membershipRef != null && membershipRef.isValid() ? componentAccessor.getComponent(membershipRef, Flock.getComponentType()) : null;
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public static Ref<EntityStore> createFlock(@Nonnull Store<EntityStore> store, @Nonnull Role role) {
|
|
return createFlock(store, null, role.getFlockAllowedRoles());
|
|
}
|
|
|
|
@Nonnull
|
|
public static Ref<EntityStore> createFlock(@Nonnull Store<EntityStore> store, @Nullable FlockAsset flockDefinition, @Nonnull String[] allowedRoles) {
|
|
Holder<EntityStore> holder = EntityStore.REGISTRY.newHolder();
|
|
holder.addComponent(UUIDComponent.getComponentType(), UUIDComponent.randomUUID());
|
|
holder.addComponent(EntityGroup.getComponentType(), new EntityGroup());
|
|
holder.addComponent(Flock.getComponentType(), new Flock(flockDefinition, allowedRoles));
|
|
Ref<EntityStore> ref = store.addEntity(holder, AddReason.SPAWN);
|
|
if (ref == null) {
|
|
throw new UnsupportedOperationException("Unable to handle non-spawned flock!");
|
|
} else {
|
|
return ref;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public static Ref<EntityStore> getFlockReference(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
|
|
FlockMembership flockMembershipComponent = componentAccessor.getComponent(ref, FlockMembership.getComponentType());
|
|
return flockMembershipComponent == null ? null : flockMembershipComponent.getFlockRef();
|
|
}
|
|
|
|
public static boolean isFlockMember(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
|
|
FlockMembership flockMembershipComponent = store.getComponent(ref, FlockMembership.getComponentType());
|
|
return flockMembershipComponent != null;
|
|
}
|
|
|
|
private static final class PrefabPasteEventSystem extends WorldEventSystem<EntityStore, PrefabPasteEvent> {
|
|
private final FlockPlugin plugin;
|
|
|
|
PrefabPasteEventSystem(@Nonnull FlockPlugin plugin) {
|
|
super(PrefabPasteEvent.class);
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
public void handle(@Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer, @Nonnull PrefabPasteEvent event) {
|
|
if (event.isPasteStart()) {
|
|
this.plugin.prefabFlockRemappings.put(event.getPrefabId(), new HashMap<>());
|
|
} else {
|
|
this.plugin.prefabFlockRemappings.remove(event.getPrefabId());
|
|
}
|
|
}
|
|
}
|
|
}
|