Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mojang.datafixers.util.Either;
import earth.terrarium.heracles.Heracles;
import earth.terrarium.heracles.api.client.settings.Setting;
import earth.terrarium.heracles.client.handlers.ClientStructureDisplays;
import earth.terrarium.heracles.client.widgets.boxes.AutocompleteEditBox;
import earth.terrarium.heracles.common.utils.RegistryValue;
import net.minecraft.Optionull;
Expand Down Expand Up @@ -38,14 +39,26 @@ public AutocompleteEditBox<String> createWidget(int width, RegistryValue<T> valu
(text, item) -> item.contains(text) && !item.equals(text), Function.identity(), s -> {});
box.setMaxLength(Short.MAX_VALUE);
List<String> suggestions = new ArrayList<>();

var registry = Heracles.getRegistryAccess().registry(key).orElse(null);
if (registry == null) {
return box;
}
registry.getTagNames().map(tag -> "#" + tag.location()).forEach(suggestions::add);
registry.keySet().stream().map(ResourceLocation::toString).forEach(suggestions::add);
if (registry != null) {
// Use registry data when available
registry.getTagNames().map(tag -> "#" + tag.location()).forEach(suggestions::add);
registry.keySet().stream().map(ResourceLocation::toString).forEach(suggestions::add);
} else if (key.location().equals(Registries.STRUCTURE.location()) && ClientStructureDisplays.hasStructureData()) {
// Fallback to cached structure data for structures
ClientStructureDisplays.getStructureTags().stream()
.map(tag -> "#" + tag.toString())
.forEach(suggestions::add);
ClientStructureDisplays.getStructures().stream()
.map(ResourceLocation::toString)
.forEach(suggestions::add);
}

// Sort suggestions alphabetically for better user experience
suggestions.sort(String.CASE_INSENSITIVE_ORDER);

box.setSuggestions(suggestions);

box.setValue(Optionull.mapOrDefault(value, RegistryValue::toRegistryString, ""));
return box;
}
Expand All @@ -56,14 +69,36 @@ public RegistryValue<T> getValue(AutocompleteEditBox<String> widget) {
ResourceLocation id = ResourceLocation.tryParse(widget.getValue().substring(1));
return id == null ? null : new RegistryValue<>(Either.right(TagKey.create(key, id)));
}
var registry = Heracles.getRegistryAccess().registry(key).orElse(null);
if (registry == null) {

ResourceLocation id = ResourceLocation.tryParse(widget.getValue());
if (id == null) {
return null;
}
return Optionull.map(
ResourceLocation.tryParse(widget.getValue()), id -> registry.getHolder(ResourceKey.create(key, id))

var registry = Heracles.getRegistryAccess().registry(key).orElse(null);
if (registry != null) {
return registry.getHolder(ResourceKey.create(key, id))
.map(RegistryValue::new)
.orElse(null)
);
.orElse(null);
} else if (key.location().equals(Registries.STRUCTURE.location()) && ClientStructureDisplays.hasStructureData()) {
// Fallback for structures when registry is unavailable
if (ClientStructureDisplays.getStructures().contains(id)) {
// Try to create a standalone holder, but handle exceptions gracefully
try {
ResourceKey<T> resourceKey = ResourceKey.create(key, id);
@SuppressWarnings("unchecked")
net.minecraft.core.Holder<T> holder = (net.minecraft.core.Holder<T>)
net.minecraft.core.Holder.Reference.createStandAlone(
Heracles.getRegistryAccess().lookupOrThrow(key), resourceKey);
return new RegistryValue<>(Either.left(holder));
} catch (Exception e) {
// If holder creation fails, fall through to return null
// This is still better than crashing
}
}
}
// If we reach here, the registry is unavailable and we couldn't create a proper holder
// Return null which will cause fallback to default, but this is better than crashing
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package earth.terrarium.heracles.api.client.settings.base;

import earth.terrarium.heracles.Heracles;
import earth.terrarium.heracles.api.client.settings.Setting;
import earth.terrarium.heracles.client.handlers.ClientStructureDisplays;
import earth.terrarium.heracles.client.widgets.boxes.AutocompleteEditBox;
import net.minecraft.Optionull;
import net.minecraft.client.Minecraft;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.structure.Structure;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
* A setting that handles structure input as plain strings while providing autocomplete functionality.
* This setting stores and returns plain strings, but uses the same autocomplete data as RegistryValueSetting
* from both registry access and ClientStructureDisplays.
*/
public record StructureStringSetting() implements Setting<String, AutocompleteEditBox<String>> {

@Override
public AutocompleteEditBox<String> createWidget(int width, String value) {
AutocompleteEditBox<String> box = new AutocompleteEditBox<>(Minecraft.getInstance().font, 0, 0, width, 11,
(text, item) -> item.contains(text) && !item.equals(text), Function.identity(), s -> {});
box.setMaxLength(Short.MAX_VALUE);
List<String> suggestions = new ArrayList<>();

var registry = Heracles.getRegistryAccess().registry(Registries.STRUCTURE).orElse(null);
if (registry != null) {
// Use registry data when available
registry.getTagNames().map(tag -> "#" + tag.location()).forEach(suggestions::add);
registry.keySet().stream().map(ResourceLocation::toString).forEach(suggestions::add);
} else if (ClientStructureDisplays.hasStructureData()) {
// Fallback to cached structure data for structures
ClientStructureDisplays.getStructureTags().stream()
.map(tag -> "#" + tag.toString())
.forEach(suggestions::add);
ClientStructureDisplays.getStructures().stream()
.map(ResourceLocation::toString)
.forEach(suggestions::add);
}

// Sort suggestions alphabetically for better user experience
suggestions.sort(String.CASE_INSENSITIVE_ORDER);

box.setSuggestions(suggestions);
box.setValue(Optionull.mapOrDefault(value, Function.identity(), ""));
return box;
}

@Override
public String getValue(AutocompleteEditBox<String> widget) {
String value = widget.getValue();
if (value == null || value.trim().isEmpty()) {
return "";
}

// Validate the input format but return the string as-is
if (value.startsWith("#")) {
// Tag format: #namespace:tag_name
ResourceLocation id = ResourceLocation.tryParse(value.substring(1));
if (id == null) {
return ""; // Invalid tag format
}
} else {
// Structure format: namespace:structure_name
ResourceLocation id = ResourceLocation.tryParse(value);
if (id == null) {
return ""; // Invalid structure format
}
}

// Return the validated string as-is
return value.trim();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package earth.terrarium.heracles.api.client.settings.tasks;

import com.mojang.datafixers.util.Either;
import earth.terrarium.heracles.api.client.settings.CustomizableQuestElementSettings;
import earth.terrarium.heracles.api.client.settings.SettingInitializer;
import earth.terrarium.heracles.api.client.settings.base.RegistryValueSetting;
import earth.terrarium.heracles.api.client.settings.base.StructureStringSetting;
import earth.terrarium.heracles.api.tasks.defaults.StructureTask;
import earth.terrarium.heracles.common.utils.RegistryValue;
import net.minecraft.Optionull;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.jetbrains.annotations.Nullable;

public class StructureTaskSettings implements SettingInitializer<StructureTask>, CustomizableQuestElementSettings<StructureTask> {
Expand All @@ -14,7 +21,7 @@ public class StructureTaskSettings implements SettingInitializer<StructureTask>,
@Override
public CreationData create(@Nullable StructureTask object) {
CreationData settings = CustomizableQuestElementSettings.super.create(object);
settings.put("structure", RegistryValueSetting.STRUCTURE, Optionull.map(object, StructureTask::structures));
settings.put("structure", new StructureStringSetting(), getDefaultStructureString(object));
return settings;
}

Expand All @@ -24,7 +31,25 @@ 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))
null, // Keep structures as null for new string-based approach
data.get("structure", new StructureStringSetting()).orElse(getDefaultStructureString(object))
));
}

private static String getDefaultStructureString(StructureTask object) {
if (object != null) {
// Convert existing RegistryValue to string if available
if (object.structureString() != null) {
return object.structureString();
} else if (object.structures() != null) {
return object.structures().toRegistryString();
}
}
return "#minecraft:village"; // Default fallback
}

private static RegistryValue<Structure> getDefaultStructure(StructureTask object) {
return Optionull.mapOrDefault(object, StructureTask::structures,
new RegistryValue<>(Either.right(TagKey.create(Registries.STRUCTURE, new ResourceLocation("minecraft", "village")))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,28 @@ public static void init() {
TaskTitleFormatter.register(AdvancementTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, task.advancements().size() == 1)));
TaskTitleFormatter.register(ChangedDimensionTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, true)));
TaskTitleFormatter.register(RecipeTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, task.recipes().size() == 1), Optionull.firstOrDefault(task.titles(), CommonComponents.EMPTY)));
TaskTitleFormatter.register(StructureTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, true), task.structures().getDisplayName((id, structure) -> Component.translatableWithFallback(Util.makeDescriptionId("structure", id), id.toString()))));
TaskTitleFormatter.register(StructureTask.TYPE, (task) -> {
Component displayName;
if (task.structures() != null) {
// Use existing RegistryValue approach for backward compatibility
displayName = task.structures().getDisplayName((id, structure) -> Component.translatableWithFallback(Util.makeDescriptionId("structure", id), id.toString()));
} else if (task.structureString() != null && !task.structureString().isEmpty()) {
// Use string-based approach - create display name from string
String structureString = task.structureString();
if (structureString.startsWith("#")) {
// Tag format: #namespace:tag_name
String tagName = structureString.substring(1);
displayName = Component.translatableWithFallback("tag.structure." + tagName.replace(":", "."), tagName);
} else {
// Structure format: namespace:structure_name
displayName = Component.translatableWithFallback(Util.makeDescriptionId("structure", new ResourceLocation(structureString)), structureString);
}
} else {
// Fallback for cases where both are null/empty
displayName = Component.literal("Unknown Structure");
}
return Component.translatable(toTranslationKey(task, true), displayName);
});
TaskTitleFormatter.register(BiomeTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, true), task.biomes().getDisplayName((id, structure) -> Component.translatableWithFallback(Util.makeDescriptionId("biome", id), id.toString()))));
TaskTitleFormatter.register(BlockInteractTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, true), task.block().getDisplayName(Block::getName)));
TaskTitleFormatter.register(ItemInteractTask.TYPE, (task) -> Component.translatable(toTranslationKey(task, true), task.item().getDisplayName(Item::getDescription)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,47 @@
import earth.terrarium.heracles.api.tasks.QuestTaskType;
import earth.terrarium.heracles.api.tasks.storage.defaults.BooleanTaskStorage;
import earth.terrarium.heracles.common.utils.RegistryValue;
import earth.terrarium.heracles.common.utils.StructureResolver;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.NumericTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.levelgen.structure.Structure;
import com.mojang.datafixers.util.Either;

import java.util.Collection;

public record StructureTask(
String id, String title, QuestIcon<?> icon, RegistryValue<Structure> structures
String id, String title, QuestIcon<?> icon, RegistryValue<Structure> structures, String structureString
) implements QuestTask<Collection<Structure>, NumericTag, StructureTask>, CustomizableQuestElement {

public static final QuestTaskType<StructureTask> TYPE = new Type();

@Override
public NumericTag test(QuestTaskType<?> type, NumericTag progress, Collection<Structure> input) {
final RegistryAccess access = Heracles.getRegistryAccess();
final Registry<Structure> registry = access.registry(Registries.STRUCTURE).orElse(null);
final Registry<Structure> registry = access != null ? access.registry(Registries.STRUCTURE).orElse(null) : null;

if (registry != null) {
for (Structure structure : input) {
if (structures().is(registry.wrapAsHolder(structure))) {
return storage().of(progress, true);
// Try existing RegistryValue first
if (structures() != null) {
for (Structure structure : input) {
if (structures().is(registry.wrapAsHolder(structure))) {
return storage().of(progress, true);
}
}
} else if (structureString() != null && !structureString().isEmpty()) {
// Resolve string to RegistryValue and test
RegistryValue<Structure> resolved = StructureResolver.resolveStructureString(structureString(), access);
if (resolved != null) {
for (Structure structure : input) {
if (resolved.is(registry.wrapAsHolder(structure))) {
return storage().of(progress, true);
}
}
}
}
}
Expand Down Expand Up @@ -68,8 +86,10 @@ public Codec<StructureTask> 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)
).apply(instance, StructureTask::new));
RegistryValue.codec(Registries.STRUCTURE).optionalFieldOf("structures").forGetter(task -> java.util.Optional.ofNullable(task.structures())),
Codec.STRING.optionalFieldOf("structureString", "").forGetter(StructureTask::structureString)
).apply(instance, (taskId, title, icon, structures, structureString) ->
new StructureTask(taskId, title, icon, structures.orElse(null), structureString)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package earth.terrarium.heracles.client.handlers;

import net.minecraft.resources.ResourceLocation;

import java.util.HashSet;
import java.util.Set;

public class ClientStructureDisplays {

private static final Set<ResourceLocation> STRUCTURES = new HashSet<>();
private static final Set<ResourceLocation> STRUCTURE_TAGS = new HashSet<>();

public static void update(Set<ResourceLocation> structures, Set<ResourceLocation> structureTags) {
STRUCTURES.clear();
STRUCTURES.addAll(structures);
STRUCTURE_TAGS.clear();
STRUCTURE_TAGS.addAll(structureTags);
}

public static Set<ResourceLocation> getStructures() {
return new HashSet<>(STRUCTURES);
}

public static Set<ResourceLocation> getStructureTags() {
return new HashSet<>(STRUCTURE_TAGS);
}

public static boolean hasStructureData() {
return !STRUCTURES.isEmpty() || !STRUCTURE_TAGS.isEmpty();
}

public static void clear() {
STRUCTURES.clear();
STRUCTURE_TAGS.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.teamresourceful.resourcefullib.common.network.NetworkChannel;
import earth.terrarium.heracles.Heracles;
import earth.terrarium.heracles.common.network.packets.*;
import earth.terrarium.heracles.common.network.packets.ClientboundStructureDisplayPacket;
import earth.terrarium.heracles.common.network.packets.groups.CreateGroupPacket;
import earth.terrarium.heracles.common.network.packets.groups.DeleteGroupPacket;
import earth.terrarium.heracles.common.network.packets.groups.OpenGroupPacket;
Expand Down Expand Up @@ -37,6 +38,7 @@ public static void init() {
CHANNEL.register(QuestUnlockedPacket.TYPE);
CHANNEL.register(ClientboundAdvancementDisplayPacket.TYPE);
CHANNEL.register(ClientboundLootTablesDisplayPacket.TYPE);
CHANNEL.register(ClientboundStructureDisplayPacket.TYPE);

CHANNEL.register(OpenGroupPacket.TYPE);
CHANNEL.register(OpenQuestPacket.TYPE);
Expand Down
Loading