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

No files matched your search

-124
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
View File
@@ -130,8 +130,3 @@ bin/
### Changelog ###
changelog.txt
### Local Codex build runtime ###
.codex-jdk/
.gradle-home/
temurin-jdk21.zip
+1 -1
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
@@ -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();
@@ -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());
@@ -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 + "]}");
}
}
}
@@ -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("'", "\\'") + "'";
}
}
@@ -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 + "}}");
}
}
}
@@ -0,0 +1,8 @@
package exopandora.worldhandler.builder.argument.tag;
import java.util.List;
public interface IItemComponentProvider
{
void appendItemComponents(List<String> components);
}
@@ -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());
}));
}
@@ -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<?>>()
{
@@ -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
@@ -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();
}
}
@@ -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("");
}
}
}
@@ -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;
@@ -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;
@@ -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"