diff --git a/common/src/main/java/earth/terrarium/heracles/api/client/settings/tasks/StructureTaskSettings.java b/common/src/main/java/earth/terrarium/heracles/api/client/settings/tasks/StructureTaskSettings.java index b0e9e02f..6ed81ae4 100644 --- a/common/src/main/java/earth/terrarium/heracles/api/client/settings/tasks/StructureTaskSettings.java +++ b/common/src/main/java/earth/terrarium/heracles/api/client/settings/tasks/StructureTaskSettings.java @@ -2,6 +2,7 @@ import earth.terrarium.heracles.api.client.settings.CustomizableQuestElementSettings; import earth.terrarium.heracles.api.client.settings.SettingInitializer; +import earth.terrarium.heracles.api.client.settings.base.BooleanSetting; import earth.terrarium.heracles.api.client.settings.base.RegistryValueSetting; import earth.terrarium.heracles.api.tasks.defaults.StructureTask; import net.minecraft.Optionull; @@ -15,6 +16,7 @@ public class StructureTaskSettings implements SettingInitializer, public CreationData create(@Nullable StructureTask object) { CreationData settings = CustomizableQuestElementSettings.super.create(object); settings.put("structure", RegistryValueSetting.STRUCTURE, Optionull.map(object, StructureTask::structures)); + settings.put("accurate", BooleanSetting.FALSE, Optionull.map(object, StructureTask::accurate)); return settings; } @@ -24,7 +26,8 @@ public StructureTask create(String id, @Nullable StructureTask object, Data data id, title, icon, - data.get("structure", RegistryValueSetting.STRUCTURE).orElse(Optionull.mapOrDefault(object, StructureTask::structures, null)) + data.get("structure", RegistryValueSetting.STRUCTURE).orElse(Optionull.mapOrDefault(object, StructureTask::structures, null)), + data.get("accurate", BooleanSetting.FALSE).orElse(Optionull.mapOrDefault(object, StructureTask::accurate, false)) )); } } diff --git a/common/src/main/java/earth/terrarium/heracles/api/tasks/defaults/StructureTask.java b/common/src/main/java/earth/terrarium/heracles/api/tasks/defaults/StructureTask.java index 3cf81850..2f4f19c6 100644 --- a/common/src/main/java/earth/terrarium/heracles/api/tasks/defaults/StructureTask.java +++ b/common/src/main/java/earth/terrarium/heracles/api/tasks/defaults/StructureTask.java @@ -4,13 +4,17 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import earth.terrarium.heracles.Heracles; import earth.terrarium.heracles.api.CustomizableQuestElement; +import earth.terrarium.heracles.api.quests.Quest; import earth.terrarium.heracles.api.quests.QuestIcon; import earth.terrarium.heracles.api.quests.QuestIcons; import earth.terrarium.heracles.api.quests.defaults.ItemQuestIcon; +import earth.terrarium.heracles.api.tasks.CacheableQuestTaskType; import earth.terrarium.heracles.api.tasks.QuestTask; import earth.terrarium.heracles.api.tasks.QuestTaskType; import earth.terrarium.heracles.api.tasks.storage.defaults.BooleanTaskStorage; import earth.terrarium.heracles.common.utils.RegistryValue; +import it.unimi.dsi.fastutil.booleans.BooleanObjectPair; +import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; @@ -19,19 +23,25 @@ import net.minecraft.world.level.levelgen.structure.Structure; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; public record StructureTask( - String id, String title, QuestIcon icon, RegistryValue structures -) implements QuestTask, NumericTag, StructureTask>, CustomizableQuestElement { + String id, String title, QuestIcon icon, RegistryValue structures, boolean accurate +) implements QuestTask>, NumericTag, StructureTask>, CustomizableQuestElement { - public static final QuestTaskType TYPE = new Type(); + public static final CacheableQuestTaskType TYPE = new Type(); @Override - public NumericTag test(QuestTaskType type, NumericTag progress, Collection input) { + public NumericTag test(QuestTaskType type, NumericTag progress, BooleanObjectPair> input) { + final Collection structures = input.right(); + final boolean accurate = input.leftBoolean(); + if (this.accurate && !accurate) return progress; + final RegistryAccess access = Heracles.getRegistryAccess(); final Registry registry = access.registry(Registries.STRUCTURE).orElse(null); if (registry != null) { - for (Structure structure : input) { + for (Structure structure : structures) { if (structures().is(registry.wrapAsHolder(structure))) { return storage().of(progress, true); } @@ -55,7 +65,7 @@ public QuestTaskType type() { return TYPE; } - private static class Type implements QuestTaskType { + private static class Type implements CacheableQuestTaskType { @Override public ResourceLocation id() { @@ -68,8 +78,31 @@ public Codec codec(String id) { RecordCodecBuilder.point(id), Codec.STRING.optionalFieldOf("title", "").forGetter(StructureTask::title), QuestIcons.CODEC.optionalFieldOf("icon", ItemQuestIcon.AIR).forGetter(StructureTask::icon), - RegistryValue.codec(Registries.STRUCTURE).fieldOf("structures").forGetter(StructureTask::structures) + RegistryValue.codec(Registries.STRUCTURE).fieldOf("structures").forGetter(StructureTask::structures), + Codec.BOOL.optionalFieldOf("accurate", false).forGetter(StructureTask::accurate) ).apply(instance, StructureTask::new)); } + + @Override + public Cache cache(Collection quests) { + final RegistryAccess access = Heracles.getRegistryAccess(); + final Registry registry = access.registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return null; + final Cache cache = new Cache(new HashSet<>(), new HashSet<>()); + for (Quest quest : quests) { + for (QuestTask task : quest.tasks().values()) { + if (task instanceof StructureTask structureTask) { + var structures = structureTask.accurate() ? cache.accurate() : cache.inaccurate(); + structureTask.structures.value() + .ifLeft(structures::add) + .ifRight(key -> registry.getTag(key).ifPresent(tag -> tag.forEach(structures::add))); + } + } + } + return cache; + } + } + + public record Cache(Set> accurate, Set> inaccurate) { } } diff --git a/common/src/main/java/earth/terrarium/heracles/common/handlers/TaskManager.java b/common/src/main/java/earth/terrarium/heracles/common/handlers/TaskManager.java new file mode 100644 index 00000000..45bcdb4d --- /dev/null +++ b/common/src/main/java/earth/terrarium/heracles/common/handlers/TaskManager.java @@ -0,0 +1,87 @@ +package earth.terrarium.heracles.common.handlers; + +import earth.terrarium.heracles.api.tasks.defaults.BiomeTask; +import earth.terrarium.heracles.api.tasks.defaults.LocationTask; +import earth.terrarium.heracles.api.tasks.defaults.StructureTask; +import earth.terrarium.heracles.common.handlers.progress.QuestProgressHandler; +import earth.terrarium.heracles.common.handlers.progress.QuestsProgress; +import earth.terrarium.heracles.common.handlers.quests.QuestHandler; +import it.unimi.dsi.fastutil.booleans.BooleanObjectPair; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructurePiece; +import net.minecraft.world.level.levelgen.structure.StructureStart; + +import java.util.HashMap; +import java.util.Map; + +public class TaskManager { + + public static void onPlayerTick(ServerPlayer player) { + if ((player.tickCount + player.getId()) % 20 != 0) return; + + QuestsProgress progress = QuestProgressHandler.getProgress(player.server, player.getUUID()); + checkLocation(progress, player); + checkStructures(progress, player); + } + + private static void checkLocation(QuestsProgress progress, ServerPlayer player) { + progress.testAndProgressTaskType(player, player.level().getBiome(player.getOnPos()), BiomeTask.TYPE); + progress.testAndProgressTaskType(player, player, LocationTask.TYPE); + } + + private static void checkStructures(QuestsProgress progress, ServerPlayer player) { + if (!QuestHandler.isTaskUsed(StructureTask.TYPE)) return; + StructureTask.Cache cache = QuestHandler.getTaskCache(StructureTask.TYPE); + if (cache == null) return; + var structures = player.serverLevel().structureManager().getAllStructuresAt(player.getOnPos()); + if (structures.isEmpty()) return; + checkStructuresAccurately(cache, progress, player, structures); + checkStructuresInaccurately(cache, progress, player, structures); + } + + private static void checkStructuresAccurately(StructureTask.Cache cache, QuestsProgress progress, ServerPlayer player, Map structures) { + if (!cache.accurate().isEmpty()) return; + structures = new HashMap<>(structures); + var registry = player.server.registryAccess().registry(Registries.STRUCTURE).orElse(null); + if (registry != null) { + structures.entrySet().removeIf(entry -> { + ResourceKey key = registry.getResourceKey(entry.getKey()).orElse(null); + if (key == null) return true; + return cache.accurate().stream().noneMatch(holder -> holder.is(key)); + }); + } + + ServerLevel level = player.serverLevel(); + + structures.entrySet().removeIf(entry -> { + for (Long pos : entry.getValue()) { + int x = ChunkPos.getX(pos); + int z = ChunkPos.getZ(pos); + StructureStart start = level.getChunk(x, z, ChunkStatus.STRUCTURE_STARTS).getStartForStructure(entry.getKey()); + if (start == null) continue; + if (start.getBoundingBox().isInside(player.blockPosition())) { + for (StructurePiece piece : start.getPieces()) { + if (piece.getBoundingBox().isInside(player.blockPosition())) { + return false; + } + } + } + } + return true; + }); + + progress.testAndProgressTaskType(player, BooleanObjectPair.of(true, structures.keySet()), StructureTask.TYPE); + } + + private static void checkStructuresInaccurately(StructureTask.Cache cache, QuestsProgress progress, ServerPlayer player, Map structures) { + if (!cache.inaccurate().isEmpty()) return; + progress.testAndProgressTaskType(player, BooleanObjectPair.of(false, structures.keySet()), StructureTask.TYPE); + } +} diff --git a/fabric/src/main/java/earth/terrarium/heracles/mixins/fabric/ServerPlayerMixin.java b/fabric/src/main/java/earth/terrarium/heracles/mixins/fabric/ServerPlayerMixin.java index 4a0ce71b..2e22426f 100644 --- a/fabric/src/main/java/earth/terrarium/heracles/mixins/fabric/ServerPlayerMixin.java +++ b/fabric/src/main/java/earth/terrarium/heracles/mixins/fabric/ServerPlayerMixin.java @@ -1,19 +1,14 @@ package earth.terrarium.heracles.mixins.fabric; import com.mojang.authlib.GameProfile; -import earth.terrarium.heracles.api.tasks.defaults.BiomeTask; import earth.terrarium.heracles.api.tasks.defaults.ItemUseTask; -import earth.terrarium.heracles.api.tasks.defaults.LocationTask; -import earth.terrarium.heracles.api.tasks.defaults.StructureTask; +import earth.terrarium.heracles.common.handlers.TaskManager; import earth.terrarium.heracles.common.handlers.progress.QuestProgressHandler; -import earth.terrarium.heracles.common.handlers.progress.QuestsProgress; -import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; -import net.minecraft.world.level.levelgen.structure.Structure; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -21,8 +16,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Map; - @Mixin(ServerPlayer.class) public abstract class ServerPlayerMixin extends Player { @Shadow @@ -41,18 +34,6 @@ public ServerPlayerMixin(Level level, BlockPos blockPos, float f, GameProfile ga @Inject(method = "tick", at = @At("TAIL")) private void heracles$tick(CallbackInfo ci) { - if (tickCount % 20 == 0) { - ServerPlayer player = (ServerPlayer) (Object) this; - - QuestsProgress progress = QuestProgressHandler.getProgress(player.server, player.getUUID()); - Map structures = player.serverLevel().structureManager().getAllStructuresAt(player.getOnPos()); - - progress.testAndProgressTaskType(player, player.level().getBiome(player.getOnPos()), BiomeTask.TYPE); - progress.testAndProgressTaskType(player, player, LocationTask.TYPE); - - if (!structures.isEmpty()) { - progress.testAndProgressTaskType(player, structures.keySet(), StructureTask.TYPE); - } - } + TaskManager.onPlayerTick((ServerPlayer) (Object) this); } } diff --git a/forge/src/main/java/earth/terrarium/heracles/forge/HeraclesForge.java b/forge/src/main/java/earth/terrarium/heracles/forge/HeraclesForge.java index d9d3e319..da936851 100644 --- a/forge/src/main/java/earth/terrarium/heracles/forge/HeraclesForge.java +++ b/forge/src/main/java/earth/terrarium/heracles/forge/HeraclesForge.java @@ -3,13 +3,11 @@ import earth.terrarium.heracles.Heracles; import earth.terrarium.heracles.api.tasks.defaults.*; import earth.terrarium.heracles.common.commands.ModCommands; +import earth.terrarium.heracles.common.handlers.TaskManager; import earth.terrarium.heracles.common.handlers.progress.QuestProgressHandler; -import earth.terrarium.heracles.common.handlers.progress.QuestsProgress; import earth.terrarium.heracles.common.utils.PlatformSettings; -import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockSourceImpl; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.TickEvent; @@ -22,8 +20,6 @@ import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLPaths; -import java.util.Map; - @Mod(Heracles.MOD_ID) public class HeraclesForge { @@ -68,16 +64,7 @@ private static void onAdvancementEarn(AdvancementEvent.AdvancementEarnEvent even private static void onTick(TickEvent.PlayerTickEvent event) { if (event.player.tickCount % 20 != 0) return; if (!(event.player instanceof ServerPlayer player)) return; - - QuestsProgress progress = QuestProgressHandler.getProgress(player.server, player.getUUID()); - Map structures = player.serverLevel().structureManager().getAllStructuresAt(player.getOnPos()); - - progress.testAndProgressTaskType(player, player.serverLevel().getBiome(player.getOnPos()), BiomeTask.TYPE); - progress.testAndProgressTaskType(player, player, LocationTask.TYPE); - - if (!structures.isEmpty()) { - progress.testAndProgressTaskType(player, structures.keySet(), StructureTask.TYPE); - } + TaskManager.onPlayerTick(player); } private static void onItemUse(LivingEntityUseItemEvent.Finish event) {