2 Commits

Author SHA1 Message Date
Codex
8a91106d5a fix: address NeoForge GUI runtime issues
Avoid passing null narration text to GuiHintTextField, emit item components for custom item display/enchantment/attribute data, and update the player preview render call for the 1.21.1 InventoryScreen API.

Validated with ./gradlew.bat build --no-daemon.
2026-04-27 00:53:18 +02:00
Codex
193d93069e feat: add NeoForge 1.21.1 support
Port WorldHandler to Minecraft 1.21.1 on NeoForge 21.1.225.

This migrates the build from ForgeGradle to NeoForge ModDevGradle, updates mod metadata, replaces Forge API usage with NeoForge equivalents, updates registry access for 1.21.1, and avoids a client-startup advancement reload that can hang large modpacks.

Validated with compileJava and full Gradle build.
2026-04-26 20:54:01 +02:00
17 changed files with 152 additions and 149 deletions

View File

@@ -1,124 +0,0 @@
name: Build
on:
push:
branches-ignore:
- artifacts
tags:
- "v*"
pull_request:
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Make Gradle wrapper executable
run: chmod +x ./gradlew
- name: Build
run: ./gradlew build --no-daemon
- name: Create Gitea release
if: startsWith(github.ref, 'refs/tags/')
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
api="${GITHUB_SERVER_URL}/api/v1"
release_name="WorldHandler ${tag}"
jar="$(find build/libs -maxdepth 1 -name '*neoforge*.jar' -print -quit)"
token="${GITEA_TOKEN:-${GITHUB_TOKEN:-}}"
if [ -z "${jar}" ]; then
echo "No NeoForge jar found in build/libs" >&2
exit 1
fi
if [ -z "${token}" ]; then
echo "No Gitea token available" >&2
exit 1
fi
release_json="$(mktemp)"
if ! curl -fsS \
-H "Authorization: token ${token}" \
-H "Accept: application/json" \
"${api}/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" \
-o "${release_json}"; then
export tag release_name GITHUB_SHA
python3 -c 'import json, os; print(json.dumps({"tag_name": os.environ["tag"], "target_commitish": os.environ["GITHUB_SHA"], "name": os.environ["release_name"], "body": "NeoForge 21.1.225 build for Minecraft 1.21.1.", "draft": False, "prerelease": False}))' > "${release_json}"
curl -fsS \
-X POST \
-H "Authorization: token ${token}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data-binary @"${release_json}" \
"${api}/repos/${GITHUB_REPOSITORY}/releases" \
-o "${release_json}"
fi
release_id="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "${release_json}")"
asset_name="$(basename "${jar}")"
python3 -c 'import json, sys; release = json.load(open(sys.argv[1])); asset_name = sys.argv[2]; [print(asset["id"]) for asset in release.get("assets", []) if asset.get("name") == asset_name]' "${release_json}" "${asset_name}" > assets-to-delete.txt
while read -r asset_id; do
[ -z "${asset_id}" ] && continue
curl -fsS \
-X DELETE \
-H "Authorization: token ${token}" \
"${api}/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets/${asset_id}"
done < assets-to-delete.txt
curl -fsS \
-X POST \
-H "Authorization: token ${token}" \
-H "Accept: application/json" \
-F "attachment=@${jar};type=application/java-archive" \
"${api}/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets?name=${asset_name}"
- name: Publish jar to artifacts branch
if: github.event_name != 'pull_request'
shell: bash
run: |
set -euo pipefail
mkdir -p /tmp/worldhandler-artifacts
cp build/libs/*.jar /tmp/worldhandler-artifacts/
git config user.name "Gitea Actions"
git config user.email "actions@gitea.local"
git fetch origin artifacts || true
git switch --force-create artifacts
git rm -rf .
cp /tmp/worldhandler-artifacts/*.jar .
cat > README.md <<'EOF'
# WorldHandler build artifacts
This branch is maintained by the Gitea runner.
Download the jar file from this branch.
EOF
git add README.md *.jar
git commit --allow-empty -m "Publish WorldHandler jar from ${GITHUB_SHA}"
git push --force origin artifacts

5
.gitignore vendored
View File

@@ -130,8 +130,3 @@ bin/
### Changelog ###
changelog.txt
### Local Codex build runtime ###
.codex-jdk/
.gradle-home/
temurin-jdk21.zip

View File

@@ -10,7 +10,7 @@ author = Exopandora
# NeoForge
neo_version = 21.1.225
loader_version_range = [4,)
neoforge_compatible_minecraft_versions = 1.21.1
neoforge_compatible_minecraft_versions = 1.21,1.21.1
# Publishing
curse_project_id = 228970

View File

@@ -4,6 +4,10 @@ import javax.annotation.Nullable;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.ArrayList;
import java.util.List;
import exopandora.worldhandler.builder.argument.tag.IItemComponentProvider;
import exopandora.worldhandler.util.ItemPredicateParser;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
@@ -12,6 +16,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
public class ItemArgument extends TagArgument
{
private Item item;
private List<IItemComponentProvider> componentProviders;
protected ItemArgument()
{
@@ -39,6 +44,16 @@ public class ItemArgument extends TagArgument
{
return this.item;
}
public void addComponentProvider(IItemComponentProvider provider)
{
if(this.componentProviders == null)
{
this.componentProviders = new ArrayList<IItemComponentProvider>();
}
this.componentProviders.add(provider);
}
@Override
public void deserialize(@Nullable String string)
@@ -80,6 +95,21 @@ public class ItemArgument extends TagArgument
{
return null;
}
List<String> components = new ArrayList<String>();
if(this.componentProviders != null)
{
for(IItemComponentProvider provider : this.componentProviders)
{
provider.appendItemComponents(components);
}
}
if(!components.isEmpty())
{
return BuiltInRegistries.ITEM.getKey(this.item).toString() + "[" + String.join(",", components) + "]";
}
String tag = super.serialize();

View File

@@ -12,7 +12,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
public abstract class AbstractAttributeTag implements ITagProvider
{
public static final List<Attribute> ATTRIBUTES = BuiltInRegistries.ATTRIBUTE.stream().toList().stream()
public static final List<Attribute> ATTRIBUTES = BuiltInRegistries.ATTRIBUTE.stream()
.filter(attribute -> !attribute.getDescriptionId().equals(I18n.get(attribute.getDescriptionId())))
.collect(Collectors.toList());

View File

@@ -1,6 +1,7 @@
package exopandora.worldhandler.builder.argument.tag;
import java.util.Map.Entry;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -11,7 +12,7 @@ import net.minecraft.nbt.Tag;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.core.registries.BuiltInRegistries;
public class AttributeModifiersTag extends AbstractAttributeTag
public class AttributeModifiersTag extends AbstractAttributeTag implements IItemComponentProvider
{
@Override
@Nullable
@@ -48,4 +49,36 @@ public class AttributeModifiersTag extends AbstractAttributeTag
{
return "AttributeModifiers";
}
@Override
public void appendItemComponents(List<String> components)
{
StringBuilder modifiers = new StringBuilder();
for(Entry<Attribute, Double> entry : this.attributes.entrySet())
{
if(entry.getValue() != 0)
{
String id = BuiltInRegistries.ATTRIBUTE.getKey(entry.getKey()).toString();
if(modifiers.length() > 0)
{
modifiers.append(',');
}
modifiers.append("{type:\"")
.append(id)
.append("\",id:\"")
.append(id)
.append("\",amount:")
.append(entry.getValue() / 100)
.append(",operation:\"add_multiplied_base\"}");
}
}
if(modifiers.length() > 0)
{
components.add("minecraft:attribute_modifiers={modifiers:[" + modifiers + "]}");
}
}
}

View File

@@ -1,5 +1,8 @@
package exopandora.worldhandler.builder.argument.tag;
import java.util.ArrayList;
import java.util.List;
import exopandora.worldhandler.util.TextUtils;
import exopandora.worldhandler.util.UserStylableComponent;
import net.minecraft.nbt.CompoundTag;
@@ -8,7 +11,7 @@ import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
public class DisplayTag implements ITagProvider
public class DisplayTag implements ITagProvider, IItemComponentProvider
{
private UserStylableComponent name = new UserStylableComponent();
private Component[] lore = new Component[2];
@@ -91,4 +94,33 @@ public class DisplayTag implements ITagProvider
{
return "display";
}
@Override
public void appendItemComponents(List<String> components)
{
if(this.name.getText() != null && !this.name.getText().isEmpty())
{
components.add("minecraft:custom_name=" + quote(TextUtils.toJson(this.name)));
}
List<String> lore = new ArrayList<String>();
for(int x = 0; x < this.lore.length; x++)
{
if(this.lore[x] != null && !this.lore[x].getString().isEmpty())
{
lore.add(quote(TextUtils.toJson(this.lore[x])));
}
}
if(!lore.isEmpty())
{
components.add("minecraft:lore=[" + String.join(",", lore) + "]");
}
}
private static String quote(String value)
{
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
}
}

View File

@@ -1,6 +1,7 @@
package exopandora.worldhandler.builder.argument.tag;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -13,7 +14,7 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.item.enchantment.Enchantment;
public class EnchantmentsTag implements ITagProvider
public class EnchantmentsTag implements ITagProvider, IItemComponentProvider
{
private final Map<Enchantment, Short> enchantments = new HashMap<Enchantment, Short>();
@@ -71,4 +72,31 @@ public class EnchantmentsTag implements ITagProvider
{
return "Enchantments";
}
@Override
public void appendItemComponents(List<String> components)
{
StringBuilder levels = new StringBuilder();
for(Entry<Enchantment, Short> entry : this.enchantments.entrySet())
{
if(entry.getValue() > 0)
{
if(levels.length() > 0)
{
levels.append(',');
}
levels.append('"')
.append(RegistryHelper.getEnchantmentKey(entry.getKey()))
.append("\":")
.append(entry.getValue());
}
}
if(levels.length() > 0)
{
components.add("minecraft:enchantments={levels:{" + levels + "}}");
}
}
}

View File

@@ -0,0 +1,8 @@
package exopandora.worldhandler.builder.argument.tag;
import java.util.List;
public interface IItemComponentProvider
{
void appendItemComponents(List<String> components);
}

View File

@@ -42,22 +42,22 @@ public class ContentButcherPresets extends ContentChild
container.addRenderableWidget(new GuiButtonBase(x + 58, y, 114, 20, Component.translatable("gui.worldhandler.butcher.presets.passive_mobs"), () ->
{
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().toList().stream().filter(entity -> !MobCategory.MONSTER.equals(entity.getCategory()) && !MobCategory.MISC.equals(entity.getCategory())).collect(Collectors.toList()), this.radius);
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().filter(entity -> !MobCategory.MONSTER.equals(entity.getCategory()) && !MobCategory.MISC.equals(entity.getCategory())).collect(Collectors.toList()), this.radius);
ActionHelper.open(this.getParentContent());
}));
container.addRenderableWidget(new GuiButtonBase(x + 58, y + 24, 114, 20, Component.translatable("gui.worldhandler.butcher.presets.hostile_mobs"), () ->
{
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().toList().stream().filter(entity -> MobCategory.MONSTER.equals(entity.getCategory())).collect(Collectors.toList()), this.radius);
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().filter(entity -> MobCategory.MONSTER.equals(entity.getCategory())).collect(Collectors.toList()), this.radius);
ActionHelper.open(this.getParentContent());
}));
container.addRenderableWidget(new GuiButtonBase(x + 58, y + 48, 114, 20, Component.translatable("gui.worldhandler.butcher.presets.players"), () ->
{
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().toList().stream().filter(entity -> EntityType.PLAYER.equals(entity)).collect(Collectors.toList()), this.radius);
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().filter(entity -> EntityType.PLAYER.equals(entity)).collect(Collectors.toList()), this.radius);
ActionHelper.open(this.getParentContent());
}));
container.addRenderableWidget(new GuiButtonBase(x + 58, y + 72, 114, 20, Component.translatable("gui.worldhandler.butcher.presets.entities"), () ->
{
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().toList().stream().filter(entity -> MobCategory.MISC.equals(entity.getCategory()) && !EntityType.PLAYER.equals(entity)).collect(Collectors.toList()), this.radius);
ContentButcher.slaughter(container.getPlayer(), BuiltInRegistries.ENTITY_TYPE.stream().filter(entity -> MobCategory.MISC.equals(entity.getCategory()) && !EntityType.PLAYER.equals(entity)).collect(Collectors.toList()), this.radius);
ActionHelper.open(this.getParentContent());
}));
}

View File

@@ -24,7 +24,7 @@ public class ContentButcherSettings extends ContentChild
@Override
public void initGui(Container container, int x, int y)
{
List<EntityType<?>> list = BuiltInRegistries.ENTITY_TYPE.stream().toList().stream().filter(EntityType::canSummon).collect(Collectors.toList());
List<EntityType<?>> list = BuiltInRegistries.ENTITY_TYPE.stream().filter(EntityType::canSummon).collect(Collectors.toList());
MenuPageList<EntityType<?>> entities = new MenuPageList<EntityType<?>>(x, y, list, 114, 20, 3, container, new ILogicPageList<EntityType<?>>()
{

View File

@@ -52,9 +52,9 @@ public class ContentCustomItem extends Content
public ContentCustomItem()
{
this.builderCutomItem.item().addTagProvider(this.attributes);
this.builderCutomItem.item().addTagProvider(this.display);
this.builderCutomItem.item().addTagProvider(this.enchantments);
this.builderCutomItem.item().addComponentProvider(this.attributes);
this.builderCutomItem.item().addComponentProvider(this.display);
this.builderCutomItem.item().addComponentProvider(this.enchantments);
}
@Override

View File

@@ -188,7 +188,7 @@ public class ContentPlayer extends Content
guiGraphics.drawString(minecraft.font, minecraft.player.getName(), container.width / 2 - playerNameWidth + 59, yPos - 73, 0xE0E0E0);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, xPos, yPos, 30, xPos - mouseX, yPos - mouseY - 44, 0.0625F, (float) mouseX, (float) mouseY, minecraft.player);
InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, xPos - 30, yPos - 70, xPos + 30, yPos, 30, 0.0625F, (float) mouseX, (float) mouseY, minecraft.player);
RenderSystem.defaultBlendFunc();
}
}

View File

@@ -17,7 +17,7 @@ public class GuiHintTextField extends EditBox
public GuiHintTextField(int x, int y, int width, int height, Component hint)
{
super(Minecraft.getInstance().font, x, y, width, height, null);
super(Minecraft.getInstance().font, x, y, width, height, Component.empty());
this.setMaxLength(Integer.MAX_VALUE);
this.hint = hint;
}
@@ -60,7 +60,7 @@ public class GuiHintTextField extends EditBox
}
else
{
this.setValue((String) null);
this.setValue("");
}
}
}

View File

@@ -22,7 +22,7 @@ public class BlockPredicateParser
private static final ResourceLocation AIR_RESOURCE_LOCATION = BuiltInRegistries.BLOCK.getKey(Blocks.AIR);
private final StringReader reader;
private final Map<String, String> vagueProperties = Maps.newHashMap();
private ResourceLocation block = null;
private ResourceLocation block = AIR_RESOURCE_LOCATION;
@Nullable
private CompoundTag nbt;
private boolean isTag;

View File

@@ -19,9 +19,10 @@ import net.minecraft.core.registries.BuiltInRegistries;
public class ItemPredicateParser
{
private static final ResourceLocation AIR_RESOURCE_LOCATION = BuiltInRegistries.ITEM.getKey(Items.AIR);
private static final SimpleCommandExceptionType ERROR_NO_TAGS_ALLOWED = new SimpleCommandExceptionType(Component.translatable("argument.item.tag.disallowed"));
private final StringReader reader;
private ResourceLocation item = null;
private ResourceLocation item = AIR_RESOURCE_LOCATION;
@Nullable
private CompoundTag nbt;
private boolean isTag;

View File

@@ -18,13 +18,13 @@ license="GPL v3.0"
[[dependencies.worldhandler]]
modId="minecraft"
type="required"
versionRange="[1.21.1,1.21.2)"
versionRange="[1.21,1.21.2)"
ordering="NONE"
side="BOTH"
[[dependencies.worldhandler]]
modId="neoforge"
type="required"
versionRange="[21.1.225,)"
versionRange="[21.0.167,)"
ordering="NONE"
side="BOTH"