Files
Hytale-Server/src/com/hypixel/hytale/builtin/adventure/farming/states/CoopBlock.java
2026-01-24 10:01:30 -05:00

478 lines
22 KiB
Java

package com.hypixel.hytale.builtin.adventure.farming.states;
import com.hypixel.hytale.builtin.adventure.farming.FarmingPlugin;
import com.hypixel.hytale.builtin.adventure.farming.FarmingUtil;
import com.hypixel.hytale.builtin.adventure.farming.component.CoopResidentComponent;
import com.hypixel.hytale.builtin.adventure.farming.config.FarmingCoopAsset;
import com.hypixel.hytale.builtin.tagset.TagSetPlugin;
import com.hypixel.hytale.builtin.tagset.config.NPCGroup;
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.component.AddReason;
import com.hypixel.hytale.component.Component;
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.logger.HytaleLogger;
import com.hypixel.hytale.math.range.IntRange;
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.math.vector.Vector3i;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemDrop;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemDropList;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.entity.reference.PersistentRef;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.metadata.CapturedNPCMetadata;
import com.hypixel.hytale.server.npc.role.Role;
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 it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import javax.annotation.Nullable;
public class CoopBlock implements Component<ChunkStore> {
public static final String STATE_PRODUCE = "Produce_Ready";
public static final BuilderCodec<CoopBlock> CODEC = BuilderCodec.builder(CoopBlock.class, CoopBlock::new)
.append(new KeyedCodec<>("FarmingCoopId", Codec.STRING, true), (coop, s) -> coop.coopAssetId = s, coop -> coop.coopAssetId)
.add()
.append(
new KeyedCodec<>("Residents", new ArrayCodec<>(CoopBlock.CoopResident.CODEC, CoopBlock.CoopResident[]::new)),
(coop, residents) -> coop.residents = new ArrayList<>(Arrays.asList(residents)),
coop -> coop.residents.toArray(CoopBlock.CoopResident[]::new)
)
.add()
.append(new KeyedCodec<>("Storage", ItemContainer.CODEC), (coop, storage) -> coop.itemContainer = storage, coop -> coop.itemContainer)
.add()
.build();
protected String coopAssetId;
protected List<CoopBlock.CoopResident> residents = new ArrayList<>();
protected ItemContainer itemContainer = EmptyItemContainer.INSTANCE;
public static ComponentType<ChunkStore, CoopBlock> getComponentType() {
return FarmingPlugin.get().getCoopBlockStateComponentType();
}
public CoopBlock() {
ArrayList<ItemStack> remainder = new ArrayList<>();
this.itemContainer = ItemContainer.ensureContainerCapacity(this.itemContainer, (short)5, SimpleItemContainer::new, remainder);
}
@Nullable
public FarmingCoopAsset getCoopAsset() {
return FarmingCoopAsset.getAssetMap().getAsset(this.coopAssetId);
}
public CoopBlock(String farmingCoopId, List<CoopBlock.CoopResident> residents, ItemContainer itemContainer) {
this.coopAssetId = farmingCoopId;
this.residents.addAll(residents);
this.itemContainer = itemContainer.clone();
List<ItemStack> remainder = new ObjectArrayList<>();
this.itemContainer = ItemContainer.ensureContainerCapacity(this.itemContainer, (short)5, SimpleItemContainer::new, remainder);
}
public boolean tryPutResident(CapturedNPCMetadata metadata, WorldTimeResource worldTimeResource) {
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset == null) {
return false;
} else if (this.residents.size() >= coopAsset.getMaxResidents()) {
return false;
} else if (!this.getCoopAcceptsNPCGroup(metadata.getRoleIndex())) {
return false;
} else {
this.residents.add(new CoopBlock.CoopResident(metadata, null, worldTimeResource.getGameTime()));
return true;
}
}
public boolean tryPutWildResidentFromWild(Store<EntityStore> store, Ref<EntityStore> entityRef, WorldTimeResource worldTimeResource, Vector3i coopLocation) {
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset == null) {
return false;
} else {
NPCEntity npcComponent = store.getComponent(entityRef, NPCEntity.getComponentType());
if (npcComponent == null) {
return false;
} else {
CoopResidentComponent coopResidentComponent = store.getComponent(entityRef, CoopResidentComponent.getComponentType());
if (coopResidentComponent != null) {
return false;
} else if (!this.getCoopAcceptsNPCGroup(npcComponent.getRoleIndex())) {
return false;
} else if (this.residents.size() >= coopAsset.getMaxResidents()) {
return false;
} else {
coopResidentComponent = store.ensureAndGetComponent(entityRef, CoopResidentComponent.getComponentType());
coopResidentComponent.setCoopLocation(coopLocation);
UUIDComponent uuidComponent = store.getComponent(entityRef, UUIDComponent.getComponentType());
if (uuidComponent == null) {
return false;
} else {
PersistentRef persistentRef = new PersistentRef();
persistentRef.setEntity(entityRef, uuidComponent.getUuid());
CapturedNPCMetadata metadata = FarmingUtil.generateCapturedNPCMetadata(store, entityRef, npcComponent.getRoleIndex());
CoopBlock.CoopResident residentRecord = new CoopBlock.CoopResident(metadata, persistentRef, worldTimeResource.getGameTime());
residentRecord.deployedToWorld = true;
this.residents.add(residentRecord);
return true;
}
}
}
}
}
public boolean getCoopAcceptsNPCGroup(int npcRoleIndex) {
TagSetPlugin.TagSetLookup tagSetPlugin = TagSetPlugin.get(NPCGroup.class);
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset == null) {
return false;
} else {
int[] acceptedNpcGroupIndexes = coopAsset.getAcceptedNpcGroupIndexes();
if (acceptedNpcGroupIndexes == null) {
return true;
} else {
for (int group : acceptedNpcGroupIndexes) {
if (tagSetPlugin.tagInSet(group, npcRoleIndex)) {
return true;
}
}
return false;
}
}
}
public void generateProduceToInventory(WorldTimeResource worldTimeResource) {
Instant currentTime = worldTimeResource.getGameTime();
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset != null) {
Map<String, String> produceDropsMap = coopAsset.getProduceDrops();
if (!produceDropsMap.isEmpty()) {
ThreadLocalRandom random = ThreadLocalRandom.current();
List<ItemStack> generatedItemDrops = new ArrayList<>();
for (CoopBlock.CoopResident resident : this.residents) {
Instant lastProduced = resident.getLastProduced();
if (lastProduced == null) {
resident.setLastProduced(currentTime);
} else {
CapturedNPCMetadata residentMeta = resident.getMetadata();
int npcRoleIndex = residentMeta.getRoleIndex();
String npcName = NPCPlugin.get().getName(npcRoleIndex);
String npcDropListName = produceDropsMap.get(npcName);
if (npcDropListName != null) {
ItemDropList dropListAsset = ItemDropList.getAssetMap().getAsset(npcDropListName);
if (dropListAsset != null) {
Duration harvestDiff = Duration.between(lastProduced, currentTime);
long hoursSinceLastHarvest = harvestDiff.toHours();
int produceCount = MathUtil.ceil((float)hoursSinceLastHarvest / WorldTimeResource.HOURS_PER_DAY);
List<ItemDrop> configuredItemDrops = new ArrayList<>();
for (int i = 0; i < produceCount; i++) {
dropListAsset.getContainer().populateDrops(configuredItemDrops, random::nextDouble, npcDropListName);
for (ItemDrop drop : configuredItemDrops) {
if (drop != null && drop.getItemId() != null) {
int amount = drop.getRandomQuantity(random);
if (amount > 0) {
generatedItemDrops.add(new ItemStack(drop.getItemId(), amount, drop.getMetadata()));
}
} else {
HytaleLogger.forEnclosingClass()
.atWarning()
.log("Tried to create ItemDrop for non-existent item in drop list id '%s'", npcDropListName);
}
}
configuredItemDrops.clear();
}
resident.setLastProduced(currentTime);
}
}
}
}
this.itemContainer.addItemStacks(generatedItemDrops);
}
}
}
public void gatherProduceFromInventory(ItemContainer playerInventory) {
for (ItemStack item : this.itemContainer.removeAllItemStacks()) {
playerInventory.addItemStack(item);
}
}
public void ensureSpawnResidentsInWorld(World world, Store<EntityStore> store, Vector3d coopLocation, Vector3d spawnOffset) {
NPCPlugin npcModule = NPCPlugin.get();
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset != null) {
float radiansPerSpawn = (float) (Math.PI * 2) / coopAsset.getMaxResidents();
Vector3d spawnOffsetIteration = spawnOffset;
SpawningContext spawningContext = new SpawningContext();
for (CoopBlock.CoopResident resident : this.residents) {
CapturedNPCMetadata residentMeta = resident.getMetadata();
int npcRoleIndex = residentMeta.getRoleIndex();
boolean residentDeployed = resident.getDeployedToWorld();
PersistentRef residentEntityId = resident.getPersistentRef();
if (!residentDeployed && residentEntityId == null) {
Vector3d residentSpawnLocation = new Vector3d().assign(coopLocation).add(spawnOffsetIteration);
Builder<Role> roleBuilder = NPCPlugin.get().tryGetCachedValidRole(npcRoleIndex);
if (roleBuilder != null) {
spawningContext.setSpawnable((ISpawnableWithModel)roleBuilder);
if (spawningContext.set(world, residentSpawnLocation.x, residentSpawnLocation.y, residentSpawnLocation.z)
&& spawningContext.canSpawn() == SpawnTestResult.TEST_OK) {
Pair<Ref<EntityStore>, NPCEntity> npcPair = npcModule.spawnEntity(
store, npcRoleIndex, spawningContext.newPosition(), Vector3f.ZERO, null, null
);
if (npcPair == null) {
resident.setPersistentRef(null);
resident.setDeployedToWorld(false);
} else {
Ref<EntityStore> npcRef = npcPair.first();
NPCEntity npcComponent = npcPair.second();
npcComponent.getLeashPoint().assign(coopLocation);
if (npcRef != null && npcRef.isValid()) {
UUIDComponent uuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType());
if (uuidComponent == null) {
resident.setPersistentRef(null);
resident.setDeployedToWorld(false);
} else {
CoopResidentComponent coopResidentComponent = new CoopResidentComponent();
coopResidentComponent.setCoopLocation(coopLocation.toVector3i());
store.addComponent(npcRef, CoopResidentComponent.getComponentType(), coopResidentComponent);
PersistentRef persistentRef = new PersistentRef();
persistentRef.setEntity(npcRef, uuidComponent.getUuid());
resident.setPersistentRef(persistentRef);
resident.setDeployedToWorld(true);
spawnOffsetIteration = spawnOffsetIteration.rotateY(radiansPerSpawn);
}
} else {
resident.setPersistentRef(null);
resident.setDeployedToWorld(false);
}
}
}
}
}
}
}
}
public void ensureNoResidentsInWorld(Store<EntityStore> store) {
ArrayList<CoopBlock.CoopResident> residentsToRemove = new ArrayList<>();
for (CoopBlock.CoopResident resident : this.residents) {
boolean deployed = resident.getDeployedToWorld();
PersistentRef entityUuid = resident.getPersistentRef();
if (deployed || entityUuid != null) {
Ref<EntityStore> entityRef = entityUuid.getEntity(store);
if (entityRef == null) {
residentsToRemove.add(resident);
} else {
CoopResidentComponent coopResidentComponent = store.getComponent(entityRef, CoopResidentComponent.getComponentType());
if (coopResidentComponent == null) {
residentsToRemove.add(resident);
} else {
DeathComponent deathComponent = store.getComponent(entityRef, DeathComponent.getComponentType());
if (deathComponent != null) {
residentsToRemove.add(resident);
} else {
coopResidentComponent.setMarkedForDespawn(true);
resident.setPersistentRef(null);
resident.setDeployedToWorld(false);
}
}
}
}
}
for (CoopBlock.CoopResident residentx : residentsToRemove) {
this.residents.remove(residentx);
}
}
public boolean shouldResidentsBeInCoop(WorldTimeResource worldTimeResource) {
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset == null) {
return true;
} else {
IntRange roamTimeRange = coopAsset.getResidentRoamTime();
if (roamTimeRange == null) {
return true;
} else {
int gameHour = worldTimeResource.getCurrentHour();
return !roamTimeRange.includes(gameHour);
}
}
}
@Nullable
public Instant getNextScheduledTick(WorldTimeResource worldTimeResource) {
Instant gameTime = worldTimeResource.getGameTime();
LocalDateTime gameDateTime = worldTimeResource.getGameDateTime();
int gameHour = worldTimeResource.getCurrentHour();
int minutes = gameDateTime.getMinute();
FarmingCoopAsset coopAsset = this.getCoopAsset();
if (coopAsset == null) {
return null;
} else {
IntRange roamTimeRange = coopAsset.getResidentRoamTime();
if (roamTimeRange == null) {
return null;
} else {
int nextScheduledHour = 0;
int minTime = roamTimeRange.getInclusiveMin();
int maxTime = roamTimeRange.getInclusiveMax();
if (coopAsset.getResidentRoamTime().includes(gameHour)) {
nextScheduledHour = coopAsset.getResidentRoamTime().getInclusiveMax() + 1 - gameHour;
} else if (gameHour > maxTime) {
nextScheduledHour = WorldTimeResource.HOURS_PER_DAY - gameHour + minTime;
} else {
nextScheduledHour = minTime - gameHour;
}
return gameTime.plus(nextScheduledHour * 60L - minutes, ChronoUnit.MINUTES);
}
}
}
public void handleResidentDespawn(UUID entityUuid) {
CoopBlock.CoopResident removedResident = null;
for (CoopBlock.CoopResident resident : this.residents) {
if (resident.persistentRef != null && resident.persistentRef.getUuid() == entityUuid) {
removedResident = resident;
break;
}
}
if (removedResident != null) {
this.residents.remove(removedResident);
}
}
public void handleBlockBroken(World world, WorldTimeResource worldTimeResource, Store<EntityStore> store, int blockX, int blockY, int blockZ) {
Vector3i location = new Vector3i(blockX, blockY, blockZ);
world.execute(() -> this.ensureSpawnResidentsInWorld(world, store, location.toVector3d(), new Vector3d().assign(Vector3d.FORWARD)));
this.generateProduceToInventory(worldTimeResource);
Vector3d dropPosition = new Vector3d(blockX + 0.5F, blockY, blockZ + 0.5F);
Holder<EntityStore>[] itemEntityHolders = ItemComponent.generateItemDrops(store, this.itemContainer.removeAllItemStacks(), dropPosition, Vector3f.ZERO);
if (itemEntityHolders.length > 0) {
world.execute(() -> store.addEntities(itemEntityHolders, AddReason.SPAWN));
}
world.execute(() -> {
for (CoopBlock.CoopResident resident : this.residents) {
PersistentRef persistentRef = resident.getPersistentRef();
if (persistentRef != null) {
Ref<EntityStore> ref = persistentRef.getEntity(store);
if (ref == null) {
return;
}
store.tryRemoveComponent(ref, CoopResidentComponent.getComponentType());
}
}
});
}
public boolean hasProduce() {
return !this.itemContainer.isEmpty();
}
@Override
public Component<ChunkStore> clone() {
return new CoopBlock(this.coopAssetId, this.residents, this.itemContainer);
}
public static class CoopResident {
public static final BuilderCodec<CoopBlock.CoopResident> CODEC = BuilderCodec.builder(CoopBlock.CoopResident.class, CoopBlock.CoopResident::new)
.append(new KeyedCodec<>("Metadata", CapturedNPCMetadata.CODEC), (coop, meta) -> coop.metadata = meta, coop -> coop.metadata)
.add()
.append(
new KeyedCodec<>("PersistentRef", PersistentRef.CODEC), (coop, persistentRef) -> coop.persistentRef = persistentRef, coop -> coop.persistentRef
)
.add()
.append(
new KeyedCodec<>("DeployedToWorld", Codec.BOOLEAN), (coop, deployedToWorld) -> coop.deployedToWorld = deployedToWorld, coop -> coop.deployedToWorld
)
.add()
.append(new KeyedCodec<>("LastHarvested", Codec.INSTANT), (coop, instant) -> coop.lastProduced = instant, coop -> coop.lastProduced)
.add()
.build();
protected CapturedNPCMetadata metadata;
@Nullable
protected PersistentRef persistentRef;
protected boolean deployedToWorld;
protected Instant lastProduced;
public CoopResident() {
}
public CoopResident(CapturedNPCMetadata metadata, PersistentRef persistentRef, Instant lastProduced) {
this.metadata = metadata;
this.persistentRef = persistentRef;
this.lastProduced = lastProduced;
}
public CapturedNPCMetadata getMetadata() {
return this.metadata;
}
@Nullable
public PersistentRef getPersistentRef() {
return this.persistentRef;
}
public void setPersistentRef(@Nullable PersistentRef persistentRef) {
this.persistentRef = persistentRef;
}
public boolean getDeployedToWorld() {
return this.deployedToWorld;
}
public void setDeployedToWorld(boolean deployedToWorld) {
this.deployedToWorld = deployedToWorld;
}
public Instant getLastProduced() {
return this.lastProduced;
}
public void setLastProduced(Instant lastProduced) {
this.lastProduced = lastProduced;
}
}
}