Skip to content
Closed
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
71 changes: 71 additions & 0 deletions .github/workflows/tacz-26-2-port.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: TACZ 26.2 Port CI

on:
pull_request:
branches: [main]
workflow_dispatch:

concurrency:
group: tacz26-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- name: Clone port bases
run: |
git clone --depth 1 https://github.com/RaeYNCreations/TACZ-1.21.1-neoforge-1.21.1.git work
git clone --depth 1 --branch '26.2(main)' https://github.com/q14433686-arch/TaCZ_Refabricated_Unofficial.git fabric26
(cd work && git rev-parse HEAD) > base-commit.txt
(cd fabric26 && git rev-parse HEAD) > fabric26-commit.txt
- name: Apply migration
run: |
python3 tools/tacz26_port.py work
python3 tools/tacz26_pass4.py work
python3 tools/tacz26_pass5.py work
python3 tools/tacz26_pass6.py work
python3 tools/tacz26_pass6b.py work
- name: Compile or build
shell: bash
run: |
set +e
cd work
chmod +x gradlew
task=compileJava
if [ "${TACZ_FULL_BUILD:-0}" = "1" ]; then task=build; fi
./gradlew "$task" --stacktrace --no-daemon 2>&1 | tee ../compile.log
status=${PIPESTATUS[0]}
echo "$status" > ../compile-status.txt
exit 0
- name: Collect outputs
if: always()
run: |
mkdir -p out
find work/build/libs -maxdepth 1 -type f -name '*.jar' -exec cp {} out/ \; 2>/dev/null || true
- name: Upload diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: TACZ-26.2-port-pass
if-no-files-found: warn
path: |
compile.log
compile-status.txt
base-commit.txt
fabric26-commit.txt
out/*.jar
- name: Enforce result
if: always()
run: |
status=$(cat compile-status.txt 2>/dev/null || echo 1)
test "$status" = "0"
157 changes: 157 additions & 0 deletions tools/tacz26_pass4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
from pathlib import Path
import json
import re
import sys

root = Path(sys.argv[1]).resolve()
fabric = root.parent / "fabric26"
java = root / "src/main/java"
res = root / "src/main/resources"
fabjava = fabric / "src/main/java"


def copy_fabric(rel: str) -> Path:
src = fabjava / rel
dst = java / rel
if not src.exists():
raise FileNotFoundError(src)
dst.parent.mkdir(parents=True, exist_ok=True)
text = src.read_text(encoding="utf-8", errors="ignore")
text = text.replace("import net.fabricmc.api.EnvType;\n", "")
text = text.replace("import net.fabricmc.api.Environment;\n", "")
text = re.sub(r"\s*@Environment\(EnvType\.(?:CLIENT|SERVER)\)\s*", "\n", text)
dst.write_text(text, encoding="utf-8")
return dst


def replace_file(path: Path, pairs):
text = path.read_text(encoding="utf-8", errors="ignore")
for a, b in pairs:
text = text.replace(a, b)
path.write_text(text, encoding="utf-8")


# ---------------------------------------------------------------------------
# 26.2 GUI migration. The 26.2 Fabric source already contains the vanilla GUI
# rewrite. Only the transport and Screen accessor are loader-specific.
# ---------------------------------------------------------------------------
for rel in [
"com/tacz/guns/client/gui/GunSmithTableScreen.java",
"com/tacz/guns/client/gui/GunRefitScreen.java",
]:
p = copy_fabric(rel)
t = p.read_text(encoding="utf-8")
t = t.replace("import cn.sh1rocu.tacz.mixin.accessor.ScreenAccessor;",
"import com.tacz.guns.mixin.client.ScreenAccessor;")
t = t.replace("import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;",
"import net.neoforged.neoforge.network.PacketDistributor;")
t = t.replace("ClientPlayNetworking.send(", "PacketDistributor.sendToServer(")
p.write_text(t, encoding="utf-8")

# Local equivalent of the Fabric accessor, registered in the existing TACZ
# Mixin config so no Fabric helper package is needed at runtime.
accessor = java / "com/tacz/guns/mixin/client/ScreenAccessor.java"
accessor.parent.mkdir(parents=True, exist_ok=True)
accessor.write_text('''package com.tacz.guns.mixin.client;\n\nimport net.minecraft.client.gui.components.Renderable;\nimport net.minecraft.client.gui.screens.Screen;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.gen.Accessor;\nimport java.util.List;\n\n@Mixin(Screen.class)\npublic interface ScreenAccessor {\n @Accessor("renderables")\n List<Renderable> tacz$getRenderables();\n}\n''', encoding="utf-8")

mixins = res / "tacz.mixins.json"
if mixins.exists():
data = json.loads(mixins.read_text(encoding="utf-8"))
data["compatibilityLevel"] = "JAVA_25"
client = data.setdefault("client", [])
if "client.ScreenAccessor" not in client:
client.append("client.ScreenAccessor")
mixins.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")

# ---------------------------------------------------------------------------
# Client configs added by the 26.2 port. NeoForge uses ModConfigSpec directly.
# ---------------------------------------------------------------------------
for rel in [
"com/tacz/guns/config/client/SoundConfig.java",
"com/tacz/guns/config/client/ResourceConfig.java",
]:
p = copy_fabric(rel)
replace_file(p, [
("net.minecraftforge.common.ForgeConfigSpec", "net.neoforged.neoforge.common.ModConfigSpec"),
("ForgeConfigSpec", "ModConfigSpec"),
])

client_config = java / "com/tacz/guns/config/ClientConfig.java"
if client_config.exists():
text = client_config.read_text(encoding="utf-8")
if "ResourceConfig.init(builder);" not in text:
text = text.replace("RenderConfig.init(builder);",
"RenderConfig.init(builder);\n ResourceConfig.init(builder);\n SoundConfig.init(builder);")
if "import com.tacz.guns.config.client.*;" not in text:
text = text.replace("import com.tacz.guns.config.client.KeyConfig;\nimport com.tacz.guns.config.client.RenderConfig;\nimport com.tacz.guns.config.client.ZoomConfig;",
"import com.tacz.guns.config.client.*;")
client_config.write_text(text, encoding="utf-8")

sound_mgr = java / "com/tacz/guns/client/sound/SoundPlayManager.java"
if sound_mgr.exists():
text = sound_mgr.read_text(encoding="utf-8")
text = re.sub(r"ModSounds\.GUN(?!\.get\(\))", "ModSounds.GUN.get()", text)
sound_mgr.write_text(text, encoding="utf-8")

# ---------------------------------------------------------------------------
# Minecart target: take Mojang 26.2 behavior from the working Fabric port, then
# restore NeoForge registrations and event bus semantics.
# ---------------------------------------------------------------------------
target = copy_fabric("com/tacz/guns/entity/TargetMinecart.java")
text = target.read_text(encoding="utf-8")
text = text.replace("import cn.sh1rocu.tacz.api.LogicalSide;", "import net.neoforged.fml.LogicalSide;\nimport net.neoforged.neoforge.common.NeoForge;")
text = text.replace("import cn.sh1rocu.tacz.api.extension.IMinecart;\n", "")
text = text.replace("implements ITargetEntity, IMinecart", "implements ITargetEntity")
text = text.replace("ModSounds.TARGET_HIT,", "ModSounds.TARGET_HIT.get(),")
text = text.replace("new ItemStack(ModItems.TARGET_MINECART)", "new ItemStack(ModItems.TARGET_MINECART.get())")
text = text.replace("return ModItems.TARGET_MINECART;", "return ModItems.TARGET_MINECART.get();")
text = text.replace("return ModBlocks.TARGET.defaultBlockState();", "return ModBlocks.TARGET.get().defaultBlockState();")
text = text.replace("EntityHurtByGunEvent.Post event = new EntityHurtByGunEvent.Post(projectile, this, player, projectile.getGunId(), projectile.getGunDisplayId(), damage, Pair.of(source, source), isHeadshot, headshotMultiplier, LogicalSide.SERVER);\n EntityHurtByGunEvent.POST.invoker().post(event);",
"EntityHurtByGunEvent.Post event = new EntityHurtByGunEvent.Post(projectile, this, player, projectile.getGunId(), projectile.getGunDisplayId(), damage, Pair.of(source, source), isHeadshot, headshotMultiplier, LogicalSide.SERVER);\n NeoForge.EVENT_BUS.post(event);")
# Fabric's IMinecart mixin supplies this extension point. NeoForge does not need
# the shim for compilation; remove the method rather than pretending it overrides
# a vanilla API.
text = re.sub(r"\n\s*@Override\n\s*public boolean tacz\$canBeRidden\(\) \{\n\s*return false;\n\s*\}\n", "\n", text)
target.write_text(text, encoding="utf-8")

# ---------------------------------------------------------------------------
# Reconstruct 1.21's interpolatable walk distance on 26.2. This is a tiny,
# loader-neutral interface + NeoForge mixin, copied from the proven Fabric port.
# ---------------------------------------------------------------------------
copy_fabric("cn/sh1rocu/tacz/api/extension/IMoveDistTracker.java")
ctx = copy_fabric("com/tacz/guns/client/animation/statemachine/GunAnimationStateContext.java")
move_mixin = java / "com/tacz/guns/mixin/common/EntityMoveDistMixin.java"
move_mixin.parent.mkdir(parents=True, exist_ok=True)
move_mixin.write_text('''package com.tacz.guns.mixin.common;\n\nimport cn.sh1rocu.tacz.api.extension.IMoveDistTracker;\nimport net.minecraft.world.entity.Entity;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.Unique;\nimport org.spongepowered.asm.mixin.injection.At;\nimport org.spongepowered.asm.mixin.injection.Inject;\nimport org.spongepowered.asm.mixin.injection.callback.CallbackInfo;\n\n@Mixin(Entity.class)\npublic abstract class EntityMoveDistMixin implements IMoveDistTracker {\n @Unique private float tacz$moveDistO;\n @Unique private boolean tacz$moveDistInit;\n @Unique @Override public float tacz$getMoveDistO() {\n return this.tacz$moveDistInit ? this.tacz$moveDistO : ((Entity)(Object)this).moveDist;\n }\n @Inject(method = "tick", at = @At("HEAD"))\n private void tacz$captureMoveDistO(CallbackInfo ci) {\n this.tacz$moveDistO = ((Entity)(Object)this).moveDist;\n this.tacz$moveDistInit = true;\n }\n}\n''', encoding="utf-8")
if mixins.exists():
data = json.loads(mixins.read_text(encoding="utf-8"))
common = data.setdefault("mixins", [])
if "common.EntityMoveDistMixin" not in common:
common.append("common.EntityMoveDistMixin")
mixins.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")

# ---------------------------------------------------------------------------
# 26.2 item rendering no longer uses the old BEWLR/MultiBufferSource path. Use
# the tested 26.2 ItemModel/SpecialModelRenderer bridge from the working port.
# The registry helper itself is loader-neutral despite its historical package.
# ---------------------------------------------------------------------------
for rel in [
"cn/sh1rocu/tacz/compat/fabric/BuiltinItemRendererRegistry.java",
"com/tacz/guns/client/renderer/item/TaczDynamicItemModel.java",
"com/tacz/guns/client/renderer/item/AttachmentItemRenderer.java",
"com/tacz/guns/client/renderer/item/AmmoItemRenderer.java",
"com/tacz/guns/client/renderer/item/GunSmithTableItemRenderer.java",
]:
copy_fabric(rel)

# PlayerAnimator is optional in the core build, but ClientSetupEvent passes a
# method reference. Preserve the exact Consumer target type so javac can infer it.
pa = java / "com/tacz/guns/compat/playeranimator/PlayerAnimatorCompat.java"
if pa.exists():
text = pa.read_text(encoding="utf-8")
text = text.replace("public static void registerReloadListener(Object o){}",
"public static void registerReloadListener(java.util.function.Consumer<net.minecraft.server.packs.resources.PreparableReloadListener> register){}")
pa.write_text(text, encoding="utf-8")

print("Applied TACZ NeoForge 26.2 targeted migration pass 4")
120 changes: 120 additions & 0 deletions tools/tacz26_pass5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
from pathlib import Path
import re
import sys

root = Path(sys.argv[1]).resolve()
fabric = root.parent / "fabric26"
java = root / "src/main/java"
fabjava = fabric / "src/main/java"


def clean_env(text: str) -> str:
text = text.replace("import net.fabricmc.api.EnvType;\n", "")
text = text.replace("import net.fabricmc.api.Environment;\n", "")
return re.sub(r"\s*@Environment\(EnvType\.(?:CLIENT|SERVER)\)\s*", "\n", text)


def copy_rel(rel: str) -> Path:
src=fabjava/rel; dst=java/rel
dst.parent.mkdir(parents=True, exist_ok=True)
dst.write_text(clean_env(src.read_text(encoding='utf-8',errors='ignore')),encoding='utf-8')
return dst

# Pass 3 was intentionally conservative and retained a 1.21.1 file whenever the
# NeoForge version itself used NeoForge APIs. For 26.2 that leaves many files on
# obsolete Mojang rendering/world APIs. If the 26.2 reference version is truly
# loader-neutral, it is safe to prefer it regardless of what the old file used.
common_neo={p.relative_to(java).as_posix():p for p in java.rglob('*.java')}
common_fab={p.relative_to(fabjava).as_posix():p for p in fabjava.rglob('*.java')}
copied=0
for rel in sorted(set(common_neo)&set(common_fab)):
text=common_fab[rel].read_text(encoding='utf-8',errors='ignore')
bad=False
for line in text.splitlines():
s=line.strip()
if not s.startswith('import '): continue
imp=s.removeprefix('import ').removesuffix(';')
if imp.startswith(('net.fabricmc.fabric.api','net.fabricmc.loader.api','cn.sh1rocu.')):
bad=True; break
if not bad:
common_neo[rel].write_text(clean_env(text),encoding='utf-8')
copied+=1
print(f'Pass5: refreshed {copied} loader-neutral files from the MC26.2 source')

# 26.2 inventory abstraction used by the working port is local, small and has no
# Fabric runtime dependency. Bring it over so gun ammo inventory logic no longer
# depends on the removed 1.21 NeoForge capability surface.
for src in (fabjava/'cn/sh1rocu/tacz/util/itemhandler').rglob('*.java'):
rel=src.relative_to(fabjava).as_posix()
copy_rel(rel)
copy_rel('cn/sh1rocu/tacz/api/extension/IItem.java')

# Dynamic-renderer item implementations. Their only loader-looking imports are
# the local registry and IItem bridge already provided by pass4/pass5.
for rel in [
'com/tacz/guns/api/item/gun/AbstractGunItem.java',
'com/tacz/guns/item/AttachmentItem.java',
'com/tacz/guns/item/AmmoItem.java',
'com/tacz/guns/item/GunSmithTableItem.java',
]:
copy_rel(rel)

# ---------------------------------------------------------------------------
# Kinetic bullet: use the validated Mojang 26.2 implementation, retaining
# NeoForge's native complex-spawn packet and event bus. NeoForge still supplies
# Entity#getPersistentData(), so the temporary Fabric entity-data shim is not
# needed here.
# ---------------------------------------------------------------------------
p=copy_rel('com/tacz/guns/entity/EntityKineticBullet.java')
t=p.read_text(encoding='utf-8')
t=t.replace('import cn.sh1rocu.tacz.api.LogicalSide;', 'import net.neoforged.fml.LogicalSide;')
t=t.replace('import cn.sh1rocu.tacz.api.extension.IEntityAdditionalSpawnData;\n', 'import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;\n')
t=t.replace('import cn.sh1rocu.tacz.api.extension.IEntityPersistentData;\n', 'import net.neoforged.neoforge.common.NeoForge;\n')
t=t.replace('import net.minecraft.network.FriendlyByteBuf;', 'import net.minecraft.network.RegistryFriendlyByteBuf;')
t=t.replace('import net.minecraft.network.protocol.Packet;\n', '')
t=t.replace('import net.minecraft.network.protocol.game.ClientGamePacketListener;\n', '')
t=t.replace('import net.minecraft.server.level.ServerEntity;\n', '')
t=t.replace('implements IEntityAdditionalSpawnData', 'implements IEntityWithComplexSpawn')
# NeoForge's spawn extension generates the advanced spawn payload itself.
t=re.sub(r'\n\s*@Override\n\s*public @NotNull Packet<ClientGamePacketListener> getAddEntityPacket\(ServerEntity entity\) \{\n\s*return IEntityAdditionalSpawnData\.getEntitySpawningPacket\(this\);\n\s*\}\n', '\n', t)
t=t.replace('writeSpawnData(FriendlyByteBuf buffer)', 'writeSpawnData(RegistryFriendlyByteBuf buffer)')
t=t.replace('readSpawnData(FriendlyByteBuf additionalData)', 'readSpawnData(RegistryFriendlyByteBuf additionalData)')
t=t.replace('((IEntityPersistentData) this).tacz$getPersistentData()', 'this.getPersistentData()')
# Restore the cancellable NeoForge event semantics from the native port.
t=t.replace('EntityHurtByGunEvent.PRE.invoker().post(preEvent);', 'if (NeoForge.EVENT_BUS.post(preEvent).isCanceled()) { return; }')
t=t.replace('EntityKillByGunEvent.CALLBACK.invoker().post(killByGunEvent);', 'NeoForge.EVENT_BUS.post(killByGunEvent);')
t=t.replace('EntityHurtByGunEvent.POST.invoker().post(hurtByGunEvent);', 'NeoForge.EVENT_BUS.post(hurtByGunEvent);')
t=t.replace('AmmoHitBlockEvent.CALLBACK.invoker().post(ammoHitBlockEvent);', 'if (NeoForge.EVENT_BUS.post(ammoHitBlockEvent).isCanceled()) { return; }')
p.write_text(t,encoding='utf-8')

# ---------------------------------------------------------------------------
# 26.2 NeoForge DeferredRegister factories now inject registry-aware properties.
# Use registerBlock/registerItem for classes whose constructors take Properties.
# ---------------------------------------------------------------------------
blocks=java/'com/tacz/guns/init/ModBlocks.java'
if blocks.exists():
t=blocks.read_text(encoding='utf-8')
for name,ctor in [
('gun_smith_table','GunSmithTableBlockB'),('workbench_a','GunSmithTableBlockA'),
('workbench_b','GunSmithTableBlockB'),('workbench_c','GunSmithTableBlockC'),
('target','TargetBlock'),('statue','StatueBlock')]:
t=t.replace(f'BLOCKS.register("{name}", {ctor}::new)', f'BLOCKS.registerBlock("{name}", {ctor}::new)')
blocks.write_text(t,encoding='utf-8')

items=java/'com/tacz/guns/init/ModItems.java'
if items.exists():
t=items.read_text(encoding='utf-8')
t=t.replace('ITEMS.register("modern_kinetic_gun", ModernKineticGunItem::new)', 'ITEMS.registerItem("modern_kinetic_gun", ModernKineticGunItem::new)')
t=t.replace('ITEMS.register("target_minecart", TargetMinecartItem::new)', 'ITEMS.registerItem("target_minecart", TargetMinecartItem::new)')
t=t.replace('ITEMS.register("gun_smith_table", () -> new DefaultTableItem(ModBlocks.GUN_SMITH_TABLE.get()))', 'ITEMS.registerItem("gun_smith_table", props -> new DefaultTableItem(ModBlocks.GUN_SMITH_TABLE.get(), props))')
items.write_text(t,encoding='utf-8')

# 26.2 EventBusSubscriber no longer selects a separate MOD bus in the annotation.
for p in java.rglob('*.java'):
t=p.read_text(encoding='utf-8',errors='ignore')
t=t.replace('@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, ', '@EventBusSubscriber(')
t=t.replace('@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)', '@EventBusSubscriber')
p.write_text(t,encoding='utf-8')

print('Applied TACZ NeoForge 26.2 migration pass 5')
Loading
Loading