package com.hypixel.hytale.builtin.adventure.objectives; import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveAsset; import com.hypixel.hytale.builtin.adventure.objectives.config.ObjectiveLineAsset; import com.hypixel.hytale.builtin.adventure.objectives.config.completion.ObjectiveCompletionAsset; import com.hypixel.hytale.builtin.adventure.objectives.config.task.ObjectiveTaskAsset; import com.hypixel.hytale.builtin.adventure.objectives.config.task.TaskSet; import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveHistoryData; import com.hypixel.hytale.builtin.adventure.objectives.historydata.ObjectiveLineHistoryData; import com.hypixel.hytale.builtin.adventure.objectives.task.ObjectiveTask; import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionRecord; import com.hypixel.hytale.builtin.adventure.objectives.transaction.TransactionUtil; 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.ComponentAccessor; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.function.consumer.TriConsumer; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.protocol.packets.assets.TrackOrUpdateObjective; import com.hypixel.hytale.server.core.Message; import com.hypixel.hytale.server.core.inventory.ItemStack; import com.hypixel.hytale.server.core.io.NetworkSerializable; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class Objective implements NetworkSerializable { public static final BuilderCodec CODEC = BuilderCodec.builder(Objective.class, Objective::new) .append(new KeyedCodec<>("ObjectiveUUID", Codec.UUID_BINARY), (objective, uuid) -> objective.objectiveUUID = uuid, objective -> objective.objectiveUUID) .add() .append(new KeyedCodec<>("ObjectiveId", Codec.STRING), (objective, s) -> objective.objectiveId = s, objective -> objective.objectiveId) .add() .append( new KeyedCodec<>("ObjectiveLineData", ObjectiveLineHistoryData.CODEC), (objective, objectiveLineData) -> objective.objectiveLineHistoryData = objectiveLineData, objective -> objective.objectiveLineHistoryData ) .add() .append( new KeyedCodec<>("ObjectiveData", ObjectiveHistoryData.CODEC), (objective, objectiveHistoryData) -> objective.objectiveHistoryData = objectiveHistoryData, objective -> objective.objectiveHistoryData ) .add() .append(new KeyedCodec<>("Players", new ArrayCodec<>(Codec.UUID_STRING, UUID[]::new)), (objective, o) -> { objective.playerUUIDs = new HashSet<>(); Collections.addAll(objective.playerUUIDs, o); }, objective -> objective.playerUUIDs.toArray(UUID[]::new)) .add() .append( new KeyedCodec<>("CurrentTasks", new ArrayCodec<>(ObjectiveTask.CODEC, ObjectiveTask[]::new)), (objective, aObjectiveTasks) -> objective.currentTasks = aObjectiveTasks, objective -> objective.currentTasks ) .add() .append( new KeyedCodec<>("CurrentTaskSetIndex", Codec.INTEGER), (objective, integer) -> objective.currentTaskSetIndex = integer, objective -> objective.currentTaskSetIndex ) .add() .append(new KeyedCodec<>("WorldUUID", Codec.UUID_BINARY), (objective, s) -> objective.worldUUID = s, objective -> objective.worldUUID) .add() .append( new KeyedCodec<>("ObjectiveItemStarter", ItemStack.CODEC), (objective, itemStack) -> objective.objectiveItemStarter = itemStack, objective -> objective.objectiveItemStarter ) .add() .build(); protected UUID objectiveUUID; protected String objectiveId; @Nullable protected ObjectiveLineHistoryData objectiveLineHistoryData; protected ObjectiveHistoryData objectiveHistoryData; protected Set playerUUIDs; @Nonnull protected Set activePlayerUUIDs = ConcurrentHashMap.newKeySet(); @Nullable protected ObjectiveTask[] currentTasks; protected int currentTaskSetIndex; protected boolean completed; protected UUID worldUUID; @Nullable protected UUID markerUUID; protected boolean dirty; protected ItemStack objectiveItemStarter; public Objective( @Nonnull ObjectiveAsset asset, @Nullable UUID objectiveUUID, @Nonnull Set playerUUIDs, @Nonnull UUID worldUUID, @Nullable UUID markerUUID ) { this.objectiveId = asset.getId(); this.currentTaskSetIndex = 0; this.playerUUIDs = playerUUIDs; this.worldUUID = worldUUID; this.objectiveUUID = objectiveUUID == null ? UUID.randomUUID() : objectiveUUID; this.markerUUID = markerUUID; this.objectiveHistoryData = new ObjectiveHistoryData(asset.getId(), asset.getCategory()); } protected Objective() { } @Nonnull public UUID getObjectiveUUID() { return this.objectiveUUID; } @Nonnull public String getObjectiveId() { return this.objectiveId; } @Nullable public ObjectiveAsset getObjectiveAsset() { return ObjectiveAsset.getAssetMap().getAsset(this.objectiveId); } @Nullable public ObjectiveLineHistoryData getObjectiveLineHistoryData() { return this.objectiveLineHistoryData; } public void setObjectiveLineHistoryData(@Nullable ObjectiveLineHistoryData objectiveLineHistoryData) { this.objectiveLineHistoryData = objectiveLineHistoryData; } @Nonnull public ObjectiveHistoryData getObjectiveHistoryData() { return this.objectiveHistoryData; } @Nullable public ObjectiveLineAsset getObjectiveLineAsset() { return this.objectiveLineHistoryData == null ? null : ObjectiveLineAsset.getAssetMap().getAsset(this.objectiveLineHistoryData.getId()); } public Set getPlayerUUIDs() { return this.playerUUIDs; } @Nonnull public Set getActivePlayerUUIDs() { return this.activePlayerUUIDs; } @Nullable public ObjectiveTask[] getCurrentTasks() { return this.currentTasks; } public int getCurrentTaskSetIndex() { return this.currentTaskSetIndex; } public String getCurrentDescription() { ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); TaskSet currentTaskSet = objectiveAsset.getTaskSets()[this.currentTaskSetIndex]; return currentTaskSet.getDescriptionId() != null ? currentTaskSet.getDescriptionKey(this.objectiveId, this.currentTaskSetIndex) : objectiveAsset.getDescriptionKey(); } public boolean isCompleted() { return this.completed; } public UUID getWorldUUID() { return this.worldUUID; } @Nullable public UUID getMarkerUUID() { return this.markerUUID; } public boolean isDirty() { return this.dirty; } public ItemStack getObjectiveItemStarter() { return this.objectiveItemStarter; } public void setObjectiveItemStarter(@Nonnull ItemStack objectiveItemStarter) { this.objectiveItemStarter = objectiveItemStarter; } public boolean setup(@Nonnull Store componentAccessor) { ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); ObjectivePlugin objectiveModule = ObjectivePlugin.get(); TaskSet[] taskSets = objectiveAsset.getTaskSets(); if (this.currentTaskSetIndex >= taskSets.length) { objectiveModule.getLogger().at(Level.WARNING).log("Current taskSet index is higher than total number of taskSets for objective %s", this.objectiveId); return false; } else { ObjectiveTaskAsset[] tasks = taskSets[this.currentTaskSetIndex].getTasks(); ObjectiveTask[] newTasks = new ObjectiveTask[tasks.length]; for (int i = 0; i < tasks.length; i++) { newTasks[i] = objectiveModule.createTask(tasks[i], this.currentTaskSetIndex, i); } this.currentTasks = newTasks; return this.setupCurrentTasks(componentAccessor); } } public boolean setupCurrentTasks(@Nonnull Store store) { for (ObjectiveTask task : this.currentTasks) { if (!task.isComplete()) { TransactionRecord[] taskTransactions = task.setup(this, store); if (taskTransactions != null && TransactionUtil.anyFailed(taskTransactions)) { ObjectivePlugin.get() .getLogger() .at(Level.WARNING) .log("Failed to setup objective tasks, transaction records:%s", Arrays.toString((Object[])taskTransactions)); for (ObjectiveTask taskSetup : this.currentTasks) { taskSetup.revertTransactionRecords(); if (taskSetup == task) { break; } } return false; } } } return true; } public boolean checkTaskSetCompletion(@Nonnull Store store) { for (ObjectiveTask task : this.currentTasks) { if (!task.isComplete()) { return false; } } this.taskSetComplete(store); return true; } protected void taskSetComplete(@Nonnull Store store) { ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); this.currentTaskSetIndex++; TaskSet[] taskSets = objectiveAsset.getTaskSets(); if (this.currentTaskSetIndex < taskSets.length) { if (!this.setup(store)) { this.taskSetComplete(store); } else { TrackOrUpdateObjective trackObjectivePacket = new TrackOrUpdateObjective(this.toPacket()); this.forEachParticipant((participantReference, message, trackOrUpdateObjective) -> { PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); if (playerRefComponent != null) { playerRefComponent.sendMessage(message); playerRefComponent.getPacketHandler().writeNoCache(trackOrUpdateObjective); } }, this.getTaskInfoMessage(), trackObjectivePacket); this.checkTaskSetCompletion(store); } } else { this.complete(store); } } public void complete(@Nonnull Store store) { ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); this.forEachParticipant((participantReference, message) -> { PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); if (playerRefComponent != null) { playerRefComponent.sendMessage(message); } }, Message.translation("server.modules.objective.completed").param("title", Message.translation(objectiveAsset.getTitleKey()))); ObjectivePlugin objectiveModule = ObjectivePlugin.get(); ObjectiveCompletionAsset[] completionHandlerAssets = objectiveAsset.getCompletionHandlers(); if (completionHandlerAssets != null) { for (ObjectiveCompletionAsset objectiveCompletionAsset : completionHandlerAssets) { objectiveModule.createCompletion(objectiveCompletionAsset).handle(this, store); } } this.completed = true; objectiveModule.objectiveCompleted(this, store); } public void cancel() { for (ObjectiveTask currentTask : this.currentTasks) { currentTask.revertTransactionRecords(); } } public void unload() { for (ObjectiveTask currentTask : this.currentTasks) { currentTask.unloadTransactionRecords(); } } @Nonnull public Message getTaskInfoMessage() { Message info = Message.translation(this.getCurrentDescription()); for (ObjectiveTask task : this.currentTasks) { info.insert("\n").insert(task.getInfoMessage(this)); } return info; } public void reloadObjectiveAsset(@Nonnull Map reloadedAssets) { ObjectiveTaskAsset[] taskAssets = this.checkPossibleAssetReload(reloadedAssets); if (taskAssets != null) { World world = Universe.get().getWorld(this.worldUUID); if (world != null) { world.execute(() -> { Store store = world.getEntityStore().getStore(); ObjectiveTask[] newTasks = this.setupAndUpdateTasks(taskAssets, store); if (newTasks != null) { this.revertRemovedTasks(newTasks); this.currentTasks = newTasks; for (ObjectiveTask currentTask : this.currentTasks) { currentTask.assetChanged(this); } if (!this.checkTaskSetCompletion(store)) { TrackOrUpdateObjective updatePacket = new TrackOrUpdateObjective(this.toPacket()); this.forEachParticipant((participantReference, packet) -> { PlayerRef playerRefComponent = store.getComponent(participantReference, PlayerRef.getComponentType()); if (playerRefComponent != null) { playerRefComponent.getPacketHandler().writeNoCache(packet); } }, updatePacket); } } }); } } } @Nullable private ObjectiveTaskAsset[] checkPossibleAssetReload(@Nonnull Map reloadedAssets) { ObjectiveLineAsset objectiveLineAsset = this.getObjectiveLineAsset(); if (this.objectiveLineHistoryData != null && objectiveLineAsset == null) { this.cancel(); return null; } else { ObjectiveAsset objectiveAsset = reloadedAssets.get(this.objectiveId); if (objectiveAsset == null) { return null; } else { TaskSet[] taskSets = objectiveAsset.getTaskSets(); if (this.currentTaskSetIndex > taskSets.length) { this.cancel(); return null; } else { return taskSets[this.currentTaskSetIndex].getTasks(); } } } } @Nullable private ObjectiveTask[] setupAndUpdateTasks(@Nonnull ObjectiveTaskAsset[] taskAssets, @Nonnull Store store) { ObjectiveTask[] newTasks = new ObjectiveTask[taskAssets.length]; for (int i = 0; i < taskAssets.length; i++) { ObjectiveTaskAsset taskAsset = taskAssets[i]; ObjectiveTask objectiveTask = this.findMatchingObjectiveTask(taskAsset); if (objectiveTask != null) { objectiveTask.setAsset(taskAsset); newTasks[i] = objectiveTask; } else { ObjectiveTask newTask = newTasks[i] = ObjectivePlugin.get().createTask(taskAsset, this.currentTaskSetIndex, i); TransactionRecord[] transactionRecords = newTask.setup(this, store); if (TransactionUtil.anyFailed(transactionRecords)) { this.cancelReload(newTasks); return null; } } } return newTasks; } @Nullable private ObjectiveTask findMatchingObjectiveTask(@Nonnull ObjectiveTaskAsset taskAsset) { for (ObjectiveTask objectiveTask : this.currentTasks) { if (objectiveTask.getAsset().matchesAsset(taskAsset)) { return objectiveTask; } } return null; } private void cancelReload(@Nonnull ObjectiveTask[] newTasks) { for (ObjectiveTask taskToRevert : newTasks) { if (taskToRevert != null) { taskToRevert.revertTransactionRecords(); } } this.cancel(); this.currentTasks = null; } private void revertRemovedTasks(@Nonnull ObjectiveTask[] newTasks) { for (ObjectiveTask objectiveTask : this.currentTasks) { boolean foundMatchingTask = false; for (ObjectiveTask newTask : newTasks) { if (newTask.equals(objectiveTask)) { foundMatchingTask = true; break; } } if (!foundMatchingTask) { objectiveTask.revertTransactionRecords(); } } } public void forEachParticipant(@Nonnull Consumer> consumer) { for (UUID playerUUID : this.playerUUIDs) { PlayerRef playerRef = Universe.get().getPlayer(playerUUID); if (playerRef != null) { consumer.accept(playerRef.getReference()); } } } public void forEachParticipant(@Nonnull BiConsumer, T> consumer, T meta) { for (UUID playerUUID : this.playerUUIDs) { PlayerRef playerRef = Universe.get().getPlayer(playerUUID); if (playerRef != null) { consumer.accept(playerRef.getReference(), meta); } } } public void forEachParticipant(@Nonnull TriConsumer, T, U> consumer, @Nonnull T t, @Nonnull U u) { for (UUID playerUUID : this.playerUUIDs) { PlayerRef playerRef = Universe.get().getPlayer(playerUUID); if (playerRef != null) { consumer.accept(playerRef.getReference(), t, u); } } } @Nullable public Vector3d getPosition(@Nonnull ComponentAccessor componentAccessor) { UUID entityUUIDToFind = null; if (this.markerUUID != null) { entityUUIDToFind = this.markerUUID; } else if (!this.playerUUIDs.isEmpty()) { entityUUIDToFind = this.playerUUIDs.iterator().next(); } if (entityUUIDToFind == null) { return null; } else { World world = componentAccessor.getExternalData().getWorld(); Ref markerEntityReference = world.getEntityRef(entityUUIDToFind); if (markerEntityReference != null && markerEntityReference.isValid()) { TransformComponent transformComponent = componentAccessor.getComponent(markerEntityReference, TransformComponent.getComponentType()); return transformComponent != null ? transformComponent.getPosition() : null; } else { return null; } } } public void addActivePlayerUUID(UUID playerUUID) { this.activePlayerUUIDs.add(playerUUID); } public void removeActivePlayerUUID(UUID playerUUID) { this.activePlayerUUIDs.remove(playerUUID); } public void markDirty() { this.dirty = true; } public boolean consumeDirty() { boolean previous = this.dirty; this.dirty = false; return previous; } @Nonnull public com.hypixel.hytale.protocol.Objective toPacket() { ObjectiveAsset objectiveAsset = Objects.requireNonNull(this.getObjectiveAsset()); com.hypixel.hytale.protocol.Objective packet = new com.hypixel.hytale.protocol.Objective(); packet.objectiveUuid = this.objectiveUUID; packet.objectiveTitleKey = objectiveAsset.getTitleKey(); packet.objectiveDescriptionKey = this.getCurrentDescription(); if (this.objectiveLineHistoryData != null) { packet.objectiveLineId = this.objectiveLineHistoryData.getId(); } packet.tasks = new com.hypixel.hytale.protocol.ObjectiveTask[this.currentTasks.length]; for (int i = 0; i < this.currentTasks.length; i++) { packet.tasks[i] = this.currentTasks[i].toPacket(this); } return packet; } @Nonnull @Override public String toString() { return "Objective{objectiveUUID=" + this.objectiveUUID + ", objectiveId='" + this.objectiveId + "', objectiveLineHistoryData=" + this.objectiveLineHistoryData + ", objectiveHistoryData=" + this.objectiveHistoryData + ", playerUUIDs=" + this.playerUUIDs + ", activePlayerUUIDs=" + this.activePlayerUUIDs + ", currentTasks=" + Arrays.toString((Object[])this.currentTasks) + ", currentTaskSetIndex=" + this.currentTaskSetIndex + ", completed=" + this.completed + ", worldUUID=" + this.worldUUID + ", markerUUID=" + this.markerUUID + ", dirty=" + this.dirty + "}"; } }