diff --git a/.github/workflows/tacz-26-2-port.yml b/.github/workflows/tacz-26-2-port.yml new file mode 100644 index 0000000..8e07f89 --- /dev/null +++ b/.github/workflows/tacz-26-2-port.yml @@ -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" diff --git a/tools/tacz26_pass4.py b/tools/tacz26_pass4.py new file mode 100644 index 0000000..2dcf666 --- /dev/null +++ b/tools/tacz26_pass4.py @@ -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 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 register){}") + pa.write_text(text, encoding="utf-8") + +print("Applied TACZ NeoForge 26.2 targeted migration pass 4") diff --git a/tools/tacz26_pass5.py b/tools/tacz26_pass5.py new file mode 100644 index 0000000..fe45f78 --- /dev/null +++ b/tools/tacz26_pass5.py @@ -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 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') diff --git a/tools/tacz26_pass6.py b/tools/tacz26_pass6.py new file mode 100644 index 0000000..ae60f44 --- /dev/null +++ b/tools/tacz26_pass6.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +from pathlib import Path +import json +import re +import shutil +import subprocess +import sys + +root = Path(sys.argv[1]).resolve() +fabric = root.parent / "fabric26" +java = root / "src/main/java" +fabjava = fabric / "src/main/java" +res = root / "src/main/resources" + + +def git_restore(rel: str): + subprocess.run(["git", "-C", str(root), "checkout", "HEAD", "--", rel], check=True) + + +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_fabric(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 + + +def modernize(text: str) -> str: + text = text.replace("net.minecraft.resources.ResourceLocation", "net.minecraft.resources.Identifier") + text = re.sub(r"\bResourceLocation\b", "Identifier", text) + text = text.replace("import net.minecraft.Util;", "import net.minecraft.util.Util;") + text = text.replace("import net.minecraft.client.renderer.RenderType;", "import net.minecraft.client.renderer.rendertype.RenderType;") + text = text.replace("import net.minecraft.client.renderer.LightTexture;", "import net.minecraft.client.renderer.Lightmap;") + text = re.sub(r"\bLightTexture\b", "Lightmap", text) + text = re.sub(r"@EventBusSubscriber\(([^)]*?)\s*,?\s*bus\s*=\s*EventBusSubscriber\.Bus\.MOD\s*,?\s*([^)]*?)\)", lambda m: "@EventBusSubscriber(" + ", ".join(x.strip(" ,") for x in [m.group(1), m.group(2)] if x.strip(" ,")) + ")", text) + text = text.replace("@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)", "@EventBusSubscriber") + return text + +# Pass5 deliberately tried a broad 26.2 source transplant. A number of loader +# boundary files have identical-looking Java but completely different lifecycle +# semantics. Reset those boundaries to the known NeoForge port, then migrate +# them explicitly below. +for rel in [ + "src/main/java/com/tacz/guns/init", + "src/main/java/com/tacz/guns/client/init", + "src/main/java/com/tacz/guns/network", + "src/main/java/com/tacz/guns/config", + "src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java", + "src/main/java/com/tacz/guns/item/AttachmentItem.java", + "src/main/java/com/tacz/guns/item/AmmoItem.java", + "src/main/java/com/tacz/guns/item/GunSmithTableItem.java", +]: + git_restore(rel) + +# Remove the experimental Fabric-side inventory abstraction brought in by pass5. +# The 1.21 NeoForge gun/item core already compiled much further without it. +for rel in [ + "cn/sh1rocu/tacz/util/itemhandler", + "cn/sh1rocu/tacz/api/extension/IItem.java", +]: + p = java / rel + if p.is_dir(): shutil.rmtree(p) + elif p.exists(): p.unlink() + +# Modernize the restored loader-boundary files for Mojang/NeoForge 26.2 names. +for rel in ["com/tacz/guns/init", "com/tacz/guns/client/init", "com/tacz/guns/network", "com/tacz/guns/config"]: + for p in (java / rel).rglob("*.java"): + p.write_text(modernize(p.read_text(encoding="utf-8", errors="ignore")), encoding="utf-8") + +# --------------------------------------------------------------------------- +# Native NeoForge deferred registration. In 26.2 Block/Item constructors receive +# registry-aware Properties, so use the specialized registerBlock/registerItem +# factories instead of handing those constructors an Identifier. +# --------------------------------------------------------------------------- +blocks = java / "com/tacz/guns/init/ModBlocks.java" +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" +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("gun_smith_table", () -> new DefaultTableItem(ModBlocks.GUN_SMITH_TABLE.get()))', 'ITEMS.registerItem("gun_smith_table", props -> new DefaultTableItem(ModBlocks.GUN_SMITH_TABLE.get(), props))') +t = t.replace('ITEMS.register("target_minecart", TargetMinecartItem::new)', 'ITEMS.registerItem("target_minecart", TargetMinecartItem::new)') +items.write_text(t, encoding="utf-8") + +# Optional integrations must never stop the core port compiling. +compat = java / "com/tacz/guns/init/CompatRegistry.java" +compat.write_text('''package com.tacz.guns.init;\n\nimport net.neoforged.fml.ModList;\n\npublic final class CompatRegistry {\n public static final String IRIS = "iris";\n public static final String CLOTH_CONFIG = "cloth_config";\n private CompatRegistry() {}\n public static void init() {}\n public static void initClient() {}\n public static void checkModLoad(String id, Runnable action) { if (ModList.get().isLoaded(id)) action.run(); }\n}\n''', encoding="utf-8") +menu = java / "com/tacz/guns/compat/cloth/MenuIntegration.java" +menu.parent.mkdir(parents=True, exist_ok=True) +menu.write_text('''package com.tacz.guns.compat.cloth;\nimport net.minecraft.client.gui.screens.Screen;\npublic final class MenuIntegration { private MenuIntegration(){} public static Screen getConfigScreen(Screen parent){ return null; } }\n''', encoding="utf-8") + +# Newer 26.2 client code expects these two config groups. They are ordinary +# NeoForge ModConfigSpec entries and are safe to add to the existing client spec. +for name in ["SoundConfig", "ResourceConfig"]: + src = fabjava / f"com/tacz/guns/config/client/{name}.java" + dst = java / f"com/tacz/guns/config/client/{name}.java" + text = src.read_text(encoding="utf-8").replace("net.minecraftforge.common.ForgeConfigSpec", "net.neoforged.neoforge.common.ModConfigSpec").replace("ForgeConfigSpec", "ModConfigSpec") + dst.write_text(text, encoding="utf-8") +client_cfg = java / "com/tacz/guns/config/ClientConfig.java" +t = client_cfg.read_text(encoding="utf-8") +t = t.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.*;") +if "SoundConfig.init(builder);" not in t: + t = t.replace("RenderConfig.init(builder);", "RenderConfig.init(builder);\n ResourceConfig.init(builder);\n SoundConfig.init(builder);") +client_cfg.write_text(t, encoding="utf-8") + +# --------------------------------------------------------------------------- +# 26.2 key mapping API. Use the validated 26.2 key implementations, but dispatch +# them from NeoForge's native InputEvent and ClientTickEvent. +# --------------------------------------------------------------------------- +for name in ["AimKey", "ConfigKey", "CrawlKey", "FireSelectKey", "InspectKey", "InteractKey", "MeleeKey", "RefitKey", "ReloadKey", "ShootKey", "TaCZKeyCategory", "ZoomKey"]: + p = copy_fabric(f"com/tacz/guns/client/input/{name}.java") + text = p.read_text(encoding="utf-8") + text = text.replace("import cn.sh1rocu.tacz.api.event.InputEvent;", "import net.neoforged.neoforge.client.event.InputEvent;") + text = text.replace("import cn.sh1rocu.tacz.api.event.PlayerTickEvent;\n", "") + text = text.replace("import net.fabricmc.loader.api.FabricLoader;", "import net.neoforged.fml.ModList;") + text = text.replace("FabricLoader.getInstance().isModLoaded", "ModList.get().isLoaded") + text = text.replace("import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;", "import net.neoforged.neoforge.network.PacketDistributor;") + text = text.replace("ClientPlayNetworking.send(", "PacketDistributor.sendToServer(") + if name == "ReloadKey": + text = text.replace("public static void autoReload(PlayerTickEvent.Pre event) {\n if (!event.getEntity().level().isClientSide())\n return;\n", "public static void autoReload() {\n") + p.write_text(text, encoding="utf-8") + +subscriber = java / "com/tacz/guns/client/input/ClientInputEvents.java" +subscriber.write_text('''package com.tacz.guns.client.input;\n\nimport com.tacz.guns.GunMod;\nimport net.minecraft.client.Minecraft;\nimport net.neoforged.api.distmarker.Dist;\nimport net.neoforged.bus.api.SubscribeEvent;\nimport net.neoforged.fml.common.EventBusSubscriber;\nimport net.neoforged.neoforge.client.event.ClientTickEvent;\nimport net.neoforged.neoforge.client.event.InputEvent;\n\n@EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)\npublic final class ClientInputEvents {\n private ClientInputEvents() {}\n @SubscribeEvent public static void key(InputEvent.Key e) {\n ConfigKey.onOpenConfig(e); CrawlKey.onCrawlPress(e); FireSelectKey.onFireSelectPress(e);\n InspectKey.onInspectPress(e); InteractKey.onInteractPress(e); MeleeKey.onMeleePress(e);\n RefitKey.onRefitPress(e); ReloadKey.onReloadPress(e); ZoomKey.onZoomKeyPress(e);\n }\n @SubscribeEvent public static void mouse(InputEvent.MouseButton.Post e) {\n AimKey.onAimPress(e); ZoomKey.onZoomMousePress(e);\n }\n @SubscribeEvent public static void tick(ClientTickEvent.Post e) {\n Minecraft mc = Minecraft.getInstance();\n AimKey.onAimHoldingPreInput(mc); AimKey.cancelAim(mc); ReloadKey.autoReload(); ShootKey.autoShoot(mc, true);\n }\n}\n''', encoding="utf-8") + +# --------------------------------------------------------------------------- +# Crosshair/HUD extraction was rewritten by Mojang in 26.2. Use the proven 26.2 +# implementation and remove its Fabric-only render-tick event. Screen state is +# sampled immediately before extracting the layer instead. +# --------------------------------------------------------------------------- +cross = copy_fabric("com/tacz/guns/client/event/RenderCrosshairEvent.java") +t = cross.read_text(encoding="utf-8") +t = t.replace("import cn.sh1rocu.simplebedrockmodel.api.event.RenderTickEvent;\n", "") +t = re.sub(r"\n\s*public static void onRenderTick\(RenderTickEvent event\) \{.*?\n\s*\}\n", "\n", t, flags=re.S) +t = t.replace("LocalPlayer player = Minecraft.getInstance().player;", "isRefitScreen = Minecraft.getInstance().gui.screen() instanceof GunRefitScreen;\n LocalPlayer player = Minecraft.getInstance().player;", 1) +cross.write_text(t, encoding="utf-8") + +# Re-run annotation/name cleanup after all restores/copies. +for p in java.rglob("*.java"): + p.write_text(modernize(p.read_text(encoding="utf-8", errors="ignore")), encoding="utf-8") + +print("Applied TACZ NeoForge 26.2 migration pass 6") diff --git a/tools/tacz26_pass6b.py b/tools/tacz26_pass6b.py new file mode 100644 index 0000000..d35e786 --- /dev/null +++ b/tools/tacz26_pass6b.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + +root = Path(sys.argv[1]).resolve() +java = root / "src/main/java" + +subscriber = java / "com/tacz/guns/client/input/ClientInputEvents.java" +subscriber.write_text('''package com.tacz.guns.client.input;\n\nimport com.tacz.guns.GunMod;\nimport net.minecraft.client.Minecraft;\nimport net.neoforged.api.distmarker.Dist;\nimport net.neoforged.bus.api.SubscribeEvent;\nimport net.neoforged.fml.common.EventBusSubscriber;\nimport net.neoforged.neoforge.client.event.ClientTickEvent;\nimport net.neoforged.neoforge.client.event.InputEvent;\n\n@EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)\npublic final class ClientInputEvents {\n private ClientInputEvents() {}\n\n @SubscribeEvent\n public static void key(InputEvent.Key e) {\n ConfigKey.onOpenConfig(e);\n CrawlKey.onCrawlPress(e);\n FireSelectKey.onFireSelectKeyPress(e);\n InspectKey.onInspectPress(e);\n InteractKey.onInteractKeyPress(e);\n MeleeKey.onMeleeKeyPress(e);\n RefitKey.onRefitPress(e);\n ReloadKey.onReloadPress(e);\n ZoomKey.onZoomKeyPress(e);\n }\n\n @SubscribeEvent\n public static void mouse(InputEvent.MouseButton.Post e) {\n AimKey.onAimPress(e);\n FireSelectKey.onFireSelectMousePress(e);\n InteractKey.onInteractMousePress(e);\n MeleeKey.onMeleeMousePress(e);\n ZoomKey.onZoomMousePress(e);\n }\n\n @SubscribeEvent\n public static void tick(ClientTickEvent.Post e) {\n Minecraft mc = Minecraft.getInstance();\n AimKey.onAimHoldingPreInput(mc);\n AimKey.cancelAim(mc);\n ReloadKey.autoReload();\n ShootKey.autoShoot(mc, true);\n }\n}\n''', encoding="utf-8") + +# FancyModLoader 26.x exposes the process side through FMLEnvironment rather +# than the old static FMLLoader#getDist helper. +gunmod = java / "com/tacz/guns/GunMod.java" +if gunmod.exists(): + text = gunmod.read_text(encoding="utf-8") + text = text.replace("import net.neoforged.fml.loading.FMLLoader;", "import net.neoforged.fml.loading.FMLEnvironment;") + text = text.replace("FMLLoader.getDist()", "FMLEnvironment.getDist()") + gunmod.write_text(text, encoding="utf-8") + +print("Applied TACZ NeoForge 26.2 pass 6b") diff --git a/tools/tacz26_port.py b/tools/tacz26_port.py new file mode 100644 index 0000000..5f5b158 --- /dev/null +++ b/tools/tacz26_port.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +from pathlib import Path +import re +import shutil +import sys + +root = Path(sys.argv[1]).resolve() +fabric = root.parent / "fabric26" +java_root = root / "src/main/java" + +build = r'''plugins { + java + id("net.neoforged.moddev") version "2.0.144" +} + +group = "com.tacz" +version = "1.1.8-neoforge-26.2-port" + +base { archivesName.set("tacz-neoforge-26.2") } + +java { + toolchain.languageVersion = JavaLanguageVersion.of(25) + withSourcesJar() +} + +repositories { + mavenCentral() + maven("https://jitpack.io") { content { includeGroup("com.github.FiguraMC.luaj") } } +} + +neoForge { + version = "26.2.0.59" + runs { + create("client") { client(); gameDirectory = file("run/client") } + create("server") { server(); gameDirectory = file("run/server"); programArgument("--nogui") } + } + mods { create("tacz") { sourceSet(sourceSets["main"]) } } +} + +dependencies { + implementation("org.apache.commons:commons-math3:3.6.1") + implementation("com.github.FiguraMC.luaj:luaj-core:3.0.8-figura") + implementation("com.github.FiguraMC.luaj:luaj-jse:3.0.8-figura") + implementation("org.apache.bcel:bcel:6.6.1") +} + +tasks.withType().configureEach { + options.encoding = "UTF-8" + options.release.set(25) + options.compilerArgs.addAll(listOf("-Xmaxerrs", "3000", "-Xmaxwarns", "3000")) +} +''' +(root / "build.gradle.kts").write_text(build, encoding="utf-8") + +(root / "gradle/wrapper/gradle-wrapper.properties").write_text( + "distributionBase=GRADLE_USER_HOME\n" + "distributionPath=wrapper/dists\n" + "distributionUrl=https\\://services.gradle.org/distributions/gradle-9.2.1-bin.zip\n" + "networkTimeout=10000\nvalidateDistributionUrl=true\n" + "zipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n", encoding="utf-8") +libs = root / "gradle/libs.versions.toml" +if libs.exists(): libs.rename(libs.with_suffix(libs.suffix + ".disabled")) + +def strip_env(text: str) -> str: + 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) + return text + +def imports_with_prefix(text: str, prefixes): + out=[] + for line in text.splitlines(): + s=line.strip() + if s.startswith("import "): + imp=s.removeprefix("import ").removesuffix(";") + if imp.startswith(prefixes): out.append(imp) + return out + +# Use the already-working 26.2 Fabric port as the source of truth for Mojang's +# 26.2 API changes, while retaining NeoForge-specific event/registry/network files. +if fabric.exists(): + fab_root = fabric / "src/main/java" + neo = {p.relative_to(java_root).as_posix(): p for p in java_root.rglob("*.java")} + fab = {p.relative_to(fab_root).as_posix(): p for p in fab_root.rglob("*.java")} + copied_common=0 + for rel in sorted(set(neo) & set(fab)): + old=neo[rel].read_text(encoding="utf-8", errors="ignore") + new=fab[rel].read_text(encoding="utf-8", errors="ignore") + fi=imports_with_prefix(new,("net.fabricmc","cn.sh1rocu")) + ni=imports_with_prefix(old,("net.neoforged",)) + fabric_is_neutral=all(x in {"net.fabricmc.api.EnvType","net.fabricmc.api.Environment"} for x in fi) + neo_is_neutral=all(x in {"net.neoforged.api.distmarker.Dist","net.neoforged.api.distmarker.OnlyIn"} for x in ni) + if fabric_is_neutral and neo_is_neutral: + neo[rel].write_text(strip_env(new), encoding="utf-8") + copied_common += 1 + + # New 26.2 helper/render/data classes which do not exist in the 1.21.1 + # NeoForge tree. Only loader-neutral com.tacz classes are admitted. + copied_new=0 + allowed_prefixes=("java.","javax.","net.minecraft.","net.fabricmc.api.","com.tacz.", + "org.jetbrains.","org.joml.","com.mojang.","cn.sh1rocu.","com.google.", + "org.apache.","org.slf4j.","org.luaj.","org.lwjgl.","io.netty.","it.unimi.dsi.","static ") + for rel in sorted(set(fab)-set(neo)): + if not rel.startswith("com/tacz/guns/") or "/compat/" in rel or "/mixin/" in rel: + continue + text=fab[rel].read_text(encoding="utf-8", errors="ignore") + fi=imports_with_prefix(text,("net.fabricmc","cn.sh1rocu")) + if not all(x in {"net.fabricmc.api.EnvType","net.fabricmc.api.Environment"} for x in fi): + continue + bad=False + for line in text.splitlines(): + s=line.strip() + if not s.startswith("import "): continue + imp=s.removeprefix("import ").removesuffix(";") + if not imp.startswith(allowed_prefixes): + bad=True; break + if bad: continue + dst=java_root/rel + dst.parent.mkdir(parents=True,exist_ok=True) + dst.write_text(strip_env(text),encoding="utf-8") + copied_new += 1 + print(f"Copied {copied_common} common + {copied_new} new loader-neutral 26.2 Java files") + +# Optional integrations are not allowed to hold the core port hostage. Remove +# their old 1.21.1 implementations and optional mixins, then provide small 26.2 +# facades. Iris remains a real reflection-based compatibility bridge. +compat_dir=java_root/"com/tacz/guns/compat" +if compat_dir.exists(): shutil.rmtree(compat_dir) +for rel in [ + "com/tacz/guns/mixin/client/ar", "com/tacz/guns/mixin/client/iris", + "com/tacz/guns/mixin/carryon", "com/tacz/guns/mixin/compat" +]: + p=java_root/rel + if p.exists(): shutil.rmtree(p) + +def put(rel, text): + p=java_root/rel; p.parent.mkdir(parents=True,exist_ok=True); p.write_text(text,encoding="utf-8") + +put("com/tacz/guns/compat/ar/ARCompat.java", '''package com.tacz.guns.compat.ar;\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\npublic final class ARCompat { public static boolean LOADED=false; public static void init(){} public static boolean shouldAccelerate(){return false;} public static boolean isAccelerated(VertexConsumer v){return false;} public static void setRenderingLevel(){} public static void resetRenderingLevel(){} public static void setRenderLayer(int l){} public static void setRenderBeforeFunction(Runnable r){} public static void setRenderAfterFunction(Runnable r){} public static void resetRenderLayer(){} public static void resetRenderBeforeFunction(){} public static void resetRenderAfterFunction(){} public static void disableAcceleration(){} public static void resetAcceleration(){} public static void renderLaser(VertexConsumer v,float z,float w,boolean f,PoseStack p,int c){} }\n''') +put("com/tacz/guns/compat/controllable/ControllableCompat.java", '''package com.tacz.guns.compat.controllable;\nimport com.tacz.guns.api.item.gun.FireMode; import net.minecraft.world.item.ItemStack;\npublic final class ControllableCompat { public static void init(){} public static void onGunShoot(ItemStack s, FireMode m){} }\n''') +put("com/tacz/guns/compat/cloth/MenuIntegration.java", '''package com.tacz.guns.compat.cloth;\nimport net.minecraft.client.gui.screens.Screen;\npublic final class MenuIntegration { public static Screen getConfigScreen(Screen parent){ return null; } }\n''') +put("com/tacz/guns/compat/firstperson/FirstPersonAnimationCompat.java", '''package com.tacz.guns.compat.firstperson;\nimport net.minecraft.client.player.LocalPlayer; import net.minecraft.world.item.ItemStack;\npublic final class FirstPersonAnimationCompat { public static void init(){} public static ItemStack getMainRenderStack(LocalPlayer p){return p.getMainHandItem();} public static boolean isTaczViewmodel(ItemStack s){return true;} public static void beginDirectArmRender(){} public static void endDirectArmRender(){} }\n''') +put("com/tacz/guns/compat/immediatelyfast/ImmediatelyFastCompat.java", '''package com.tacz.guns.compat.immediatelyfast;\nimport net.minecraft.world.item.ItemStack; public final class ImmediatelyFastCompat { public static void init(){} public static void renderHotbarItem(ItemStack s,boolean pre){} public static boolean isInstalled(){return false;} }\n''') +put("com/tacz/guns/compat/shouldersurfing/ShoulderSurfingCompat.java", '''package com.tacz.guns.compat.shouldersurfing; public final class ShoulderSurfingCompat { public static void init(){} public static boolean showCrosshair(){return false;} public static boolean isInstalled(){return false;} }\n''') +put("com/tacz/guns/compat/zoomify/ZoomifyCompat.java", '''package com.tacz.guns.compat.zoomify; public final class ZoomifyCompat { public static void init(){} public static double getFov(double f,float t){return f;} }\n''') +put("com/tacz/guns/compat/playeranimator/PlayerAnimatorCompat.java", '''package com.tacz.guns.compat.playeranimator;\nimport com.tacz.guns.client.resource.GunDisplayInstance; import net.minecraft.world.entity.LivingEntity;\npublic final class PlayerAnimatorCompat { public static void init(){} public static boolean isInstalled(){return false;} public static boolean hasPlayerAnimator3rd(LivingEntity e,GunDisplayInstance d){return false;} public static void stopAllAnimation(LivingEntity e){} public static void stopAllAnimation(LivingEntity e,int f){} public static void playAnimation(LivingEntity e,GunDisplayInstance d,float l){} public static void registerReloadListener(Object o){} }\n''') +put("com/tacz/guns/compat/carryon/CarryOnReflection.java", '''package com.tacz.guns.compat.carryon;\nimport net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState;\npublic final class CarryOnReflection { public static BlockState getCarriedBlock(Player p){return null;} public static BlockEntity getCarriedBlockEntity(Player p,BlockPos b,HolderLookup.Provider h){return null;} }\n''') + +put("com/tacz/guns/compat/iris/IrisCompat.java", '''package com.tacz.guns.compat.iris;\nimport com.mojang.blaze3d.pipeline.RenderPipeline; import net.minecraft.client.renderer.SubmitNodeCollector; import net.neoforged.fml.ModList;\npublic final class IrisCompat {\n private static boolean iris(){ try{return ModList.get().isLoaded("iris");}catch(Throwable t){return false;} }\n public static void initCompat(){} public static boolean isRenderShadow(){ if(!iris())return false; try{Class c=Class.forName("net.irisshaders.iris.shadows.ShadowRenderingState"); return (Boolean)c.getMethod("areShadowsCurrentlyBeingRendered").invoke(null);}catch(Throwable t){return false;} }\n public static boolean isUsingRenderPack(){ if(!iris())return false; try{Class c=Class.forName("net.irisshaders.iris.api.v0.IrisApi"); Object i=c.getMethod("getInstance").invoke(null); return (Boolean)c.getMethod("isShaderPackInUse").invoke(i);}catch(Throwable t){return false;} }\n public static boolean assignScopePipelineToHand(RenderPipeline p,String n){ if(!iris())return false; try{Class a=Class.forName("net.irisshaders.iris.api.v0.IrisApi"); Object i=a.getMethod("getInstance").invoke(null); Class pr=Class.forName("net.irisshaders.iris.api.v0.IrisProgram"); Object hand=Enum.valueOf((Class)pr.asSubclass(Enum.class),"HAND"); a.getMethod("assignPipeline",RenderPipeline.class,pr).invoke(i,p,hand); return true;}catch(Throwable t){return false;} }\n public static void assignCommonEntityPipelinesToHandIfNeeded(){} public static boolean shouldDisableScopeMaskUnderShaderPack(){return false;}\n public static boolean isHandRendererActive(){ if(!isUsingRenderPack())return false; try{Class c=Class.forName("net.irisshaders.iris.pathways.HandRenderer"); Object i=c.getField("INSTANCE").get(null); return (Boolean)c.getMethod("isActive").invoke(i);}catch(Throwable t){return false;} }\n public static boolean endBatch(Object o){return false;} public static boolean endBatch(SubmitNodeCollector c){return false;}\n}\n''') + +# Mojang 26.2 renames for retained NeoForge-specific code. +for p in java_root.rglob("*.java"): + text=p.read_text(encoding="utf-8",errors="ignore") + text=text.replace("net.minecraft.resources.ResourceLocation","net.minecraft.resources.Identifier") + text=re.sub(r"\bResourceLocation\b","Identifier",text) + text=text.replace("import net.minecraft.Util;","import net.minecraft.util.Util;") + text=text.replace("import net.minecraft.client.renderer.RenderType;","import net.minecraft.client.renderer.rendertype.RenderType;") + text=text.replace("import net.minecraft.client.renderer.LightTexture;","import net.minecraft.client.renderer.Lightmap;") + text=re.sub(r"\bLightTexture\b","Lightmap",text) + p.write_text(text,encoding="utf-8") + +# KubeJS optional bridge. +kube=java_root/"com/tacz/guns/api/event/common/KubeJSGunEventPoster.java" +if kube.exists(): kube.write_text('''package com.tacz.guns.api.event.common;\nimport net.neoforged.bus.api.Event; public interface KubeJSGunEventPoster{ default void postEventToKubeJS(E e){} default void postClientEventToKubeJS(E e){} default void postServerEventToKubeJS(E e){} }\n''',encoding="utf-8") + +# Keep only stable public constants/hooks from the old integration registry. +compat=java_root/"com/tacz/guns/init/CompatRegistry.java" +if compat.exists(): + compat.write_text('''package com.tacz.guns.init; public final class CompatRegistry { public static final String IRIS="iris"; private CompatRegistry(){} public static void init(){} public static void initClient(){} }\n''',encoding="utf-8") + +p=root/"src/main/resources/kubejs.plugins.txt" +if p.exists(): p.write_text("",encoding="utf-8") +print("Applied TACZ NeoForge 26.2 migration pass 3")