modwright 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +212 -0
- package/assets/claude/agents/mw-explore.md +46 -0
- package/assets/claude/agents/mw-fixture.md +33 -0
- package/assets/claude/agents/mw-rule-author.md +51 -0
- package/assets/claude/agents/mw-scaffold-author.md +48 -0
- package/assets/claude/skills/mw-author.md +59 -0
- package/assets/claude/skills/mw-new-item.md +51 -0
- package/assets/claude/skills/mw-new-spell.md +50 -0
- package/assets/claude/skills/mw-scaffold.md +59 -0
- package/assets/claude/skills/mw-ship.md +57 -0
- package/assets/claude/skills/mw-validate.md +51 -0
- package/assets/claude/skills/mw-verify.md +59 -0
- package/assets/nivalisnights/extract_resources.py +177 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
- package/bridges/bg3-se/README.md +99 -0
- package/bridges/bg3-se/bridge.json +68 -0
- package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
- package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
- package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
- package/bridges/cp2077-cet/README.md +165 -0
- package/bridges/cp2077-cet/bridge.json +53 -0
- package/dist/core/bridge/arm.js +175 -0
- package/dist/core/bridge/cet-console.js +26 -0
- package/dist/core/bridge/cet-log.js +362 -0
- package/dist/core/bridge/index.js +11 -0
- package/dist/core/bridge/locate.js +35 -0
- package/dist/core/bridge/paths.js +34 -0
- package/dist/core/bridge/pending.js +89 -0
- package/dist/core/bridge/poll.js +155 -0
- package/dist/core/bridge/stage.js +100 -0
- package/dist/core/bridge/status.js +97 -0
- package/dist/core/bridge/types.js +145 -0
- package/dist/core/bridge/validate.js +107 -0
- package/dist/core/build/index.js +6 -0
- package/dist/core/build/manifest.js +47 -0
- package/dist/core/build/paths.js +32 -0
- package/dist/core/build/run.js +342 -0
- package/dist/core/build/stage.js +265 -0
- package/dist/core/build/steps/bg3.js +129 -0
- package/dist/core/build/steps/core.js +195 -0
- package/dist/core/build/steps/cyberpunk.js +95 -0
- package/dist/core/build/steps/listing.js +35 -0
- package/dist/core/build/types.js +13 -0
- package/dist/core/build/zip.js +148 -0
- package/dist/core/compat/check.js +113 -0
- package/dist/core/compat/pe.js +220 -0
- package/dist/core/compat/types.js +1 -0
- package/dist/core/compat/version.js +70 -0
- package/dist/core/deploy/guards.js +292 -0
- package/dist/core/deploy/index.js +5 -0
- package/dist/core/deploy/plan.js +696 -0
- package/dist/core/deploy/reload-apply.js +361 -0
- package/dist/core/deploy/reload.js +358 -0
- package/dist/core/deploy/sources.js +169 -0
- package/dist/core/deploy/types.js +1 -0
- package/dist/core/epic.js +54 -0
- package/dist/core/fsutil.js +191 -0
- package/dist/core/home.js +70 -0
- package/dist/core/knowledge/facts.js +229 -0
- package/dist/core/knowledge/index.js +4 -0
- package/dist/core/knowledge/search.js +112 -0
- package/dist/core/knowledge/store.js +123 -0
- package/dist/core/knowledge/types.js +1 -0
- package/dist/core/ledger/check.js +42 -0
- package/dist/core/ledger/hooks.js +80 -0
- package/dist/core/ledger/index.js +7 -0
- package/dist/core/ledger/query.js +17 -0
- package/dist/core/ledger/record.js +84 -0
- package/dist/core/ledger/status.js +53 -0
- package/dist/core/ledger/store.js +98 -0
- package/dist/core/ledger/types.js +108 -0
- package/dist/core/logs/index.js +4 -0
- package/dist/core/logs/registry.js +50 -0
- package/dist/core/logs/scan.js +195 -0
- package/dist/core/logs/triage.js +160 -0
- package/dist/core/logs/types.js +1 -0
- package/dist/core/lsx.js +10 -0
- package/dist/core/lz4.js +66 -0
- package/dist/core/mods.js +229 -0
- package/dist/core/process.js +49 -0
- package/dist/core/project/build-steps.js +83 -0
- package/dist/core/project/describe.js +74 -0
- package/dist/core/project/index.js +5 -0
- package/dist/core/project/load.js +146 -0
- package/dist/core/project/schema.js +264 -0
- package/dist/core/project/types.js +3 -0
- package/dist/core/prompts.js +72 -0
- package/dist/core/registry.js +35 -0
- package/dist/core/safety/backup.js +206 -0
- package/dist/core/safety/execute.js +188 -0
- package/dist/core/safety/index.js +5 -0
- package/dist/core/safety/plan.js +122 -0
- package/dist/core/safety/restore.js +172 -0
- package/dist/core/safety/types.js +1 -0
- package/dist/core/scaffold/bootstrap.js +7 -0
- package/dist/core/scaffold/catalog.js +28 -0
- package/dist/core/scaffold/claude-assets.js +271 -0
- package/dist/core/scaffold/collisions.js +64 -0
- package/dist/core/scaffold/emit.js +356 -0
- package/dist/core/scaffold/engine.js +680 -0
- package/dist/core/scaffold/ids.js +78 -0
- package/dist/core/scaffold/index.js +10 -0
- package/dist/core/scaffold/jsonpath.js +121 -0
- package/dist/core/scaffold/load.js +81 -0
- package/dist/core/scaffold/render.js +85 -0
- package/dist/core/scaffold/sequence.js +396 -0
- package/dist/core/scaffold/types.js +309 -0
- package/dist/core/steam.js +63 -0
- package/dist/core/surfaceutil.js +36 -0
- package/dist/core/testplan/check.js +67 -0
- package/dist/core/testplan/index.js +5 -0
- package/dist/core/testplan/load.js +100 -0
- package/dist/core/testplan/registry.js +317 -0
- package/dist/core/testplan/run.js +389 -0
- package/dist/core/testplan/types.js +82 -0
- package/dist/core/text/converters/bg3.js +50 -0
- package/dist/core/text/converters/cyberpunk.js +36 -0
- package/dist/core/text/converters/index.js +261 -0
- package/dist/core/text/diff/cr2w.js +180 -0
- package/dist/core/text/diff/index.js +16 -0
- package/dist/core/text/diff/loca.js +43 -0
- package/dist/core/text/diff/lsx.js +126 -0
- package/dist/core/text/handles.js +86 -0
- package/dist/core/text/index.js +6 -0
- package/dist/core/text/json.js +46 -0
- package/dist/core/text/templates/cache.js +71 -0
- package/dist/core/text/templates/index.js +3 -0
- package/dist/core/text/templates/library.js +245 -0
- package/dist/core/text/templates/types.js +51 -0
- package/dist/core/text/toolchain.js +16 -0
- package/dist/core/text/types.js +1 -0
- package/dist/core/text/validate.js +149 -0
- package/dist/core/text/xml.js +58 -0
- package/dist/core/toolchain/datafetch.js +151 -0
- package/dist/core/toolchain/env.js +63 -0
- package/dist/core/toolchain/features.js +170 -0
- package/dist/core/toolchain/index.js +8 -0
- package/dist/core/toolchain/install.js +191 -0
- package/dist/core/toolchain/locate.js +275 -0
- package/dist/core/toolchain/remember.js +84 -0
- package/dist/core/toolchain/run.js +318 -0
- package/dist/core/toolchain/specs.js +351 -0
- package/dist/core/toolchain/types.js +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/userconfig.js +66 -0
- package/dist/core/validate/context.js +139 -0
- package/dist/core/validate/index.js +5 -0
- package/dist/core/validate/registry.js +58 -0
- package/dist/core/validate/run.js +131 -0
- package/dist/core/validate/suppressions.js +91 -0
- package/dist/core/validate/types.js +2 -0
- package/dist/core/yamledit.js +51 -0
- package/dist/index.js +389 -0
- package/dist/server.js +1914 -0
- package/dist/surfaces/baldursgate3/compat.js +164 -0
- package/dist/surfaces/baldursgate3/identity.js +110 -0
- package/dist/surfaces/baldursgate3/index/build.js +331 -0
- package/dist/surfaces/baldursgate3/index/index.js +17 -0
- package/dist/surfaces/baldursgate3/index/info.js +40 -0
- package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
- package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
- package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
- package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
- package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
- package/dist/surfaces/baldursgate3/index/queries.js +327 -0
- package/dist/surfaces/baldursgate3/index/schema.js +62 -0
- package/dist/surfaces/baldursgate3/index.js +3 -0
- package/dist/surfaces/baldursgate3/logs.js +278 -0
- package/dist/surfaces/baldursgate3/modsettings.js +115 -0
- package/dist/surfaces/baldursgate3/pak.js +153 -0
- package/dist/surfaces/baldursgate3/settings.js +49 -0
- package/dist/surfaces/baldursgate3/surface.js +232 -0
- package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
- package/dist/surfaces/baldursgate3/validators/files.js +476 -0
- package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
- package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
- package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
- package/dist/surfaces/baldursgate3/validators/story.js +790 -0
- package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/compat.js +223 -0
- package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
- package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
- package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
- package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
- package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
- package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
- package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
- package/dist/surfaces/cyberpunk2077/index.js +3 -0
- package/dist/surfaces/cyberpunk2077/logs.js +742 -0
- package/dist/surfaces/cyberpunk2077/paths.js +12 -0
- package/dist/surfaces/cyberpunk2077/surface.js +218 -0
- package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
- package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
- package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
- package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
- package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
- package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
- package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
- package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
- package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
- package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
- package/dist/surfaces/eldenring/binders.js +111 -0
- package/dist/surfaces/eldenring/compat.js +166 -0
- package/dist/surfaces/eldenring/dcx.js +49 -0
- package/dist/surfaces/eldenring/index.js +13 -0
- package/dist/surfaces/eldenring/loaders.js +132 -0
- package/dist/surfaces/eldenring/logs.js +67 -0
- package/dist/surfaces/eldenring/paramdef.js +256 -0
- package/dist/surfaces/eldenring/params.js +129 -0
- package/dist/surfaces/eldenring/surface.js +121 -0
- package/dist/surfaces/eldenring/tools.js +52 -0
- package/dist/surfaces/eldenring/validators/binders.js +193 -0
- package/dist/surfaces/eldenring/validators/packages.js +159 -0
- package/dist/surfaces/eldenring/validators/params.js +334 -0
- package/dist/surfaces/eldenring/validators/profiles.js +287 -0
- package/dist/surfaces/index.js +34 -0
- package/dist/surfaces/nivalisnights/compat.js +229 -0
- package/dist/surfaces/nivalisnights/index/build.js +310 -0
- package/dist/surfaces/nivalisnights/index/index.js +5 -0
- package/dist/surfaces/nivalisnights/index/queries.js +231 -0
- package/dist/surfaces/nivalisnights/index.js +4 -0
- package/dist/surfaces/nivalisnights/logs.js +473 -0
- package/dist/surfaces/nivalisnights/surface.js +195 -0
- package/dist/surfaces/skyrimse/archives.js +229 -0
- package/dist/surfaces/skyrimse/compat.js +163 -0
- package/dist/surfaces/skyrimse/index.js +12 -0
- package/dist/surfaces/skyrimse/loadorder.js +174 -0
- package/dist/surfaces/skyrimse/logs.js +241 -0
- package/dist/surfaces/skyrimse/plugins.js +149 -0
- package/dist/surfaces/skyrimse/records.js +94 -0
- package/dist/surfaces/skyrimse/surface.js +240 -0
- package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
- package/dist/surfaces/skyrimse/validators/archives.js +110 -0
- package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
- package/dist/surfaces/skyrimse/validators/records.js +117 -0
- package/dist/surfaces/stardewvalley/compat.js +190 -0
- package/dist/surfaces/stardewvalley/config.js +42 -0
- package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
- package/dist/surfaces/stardewvalley/index/index.js +229 -0
- package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
- package/dist/surfaces/stardewvalley/index.js +12 -0
- package/dist/surfaces/stardewvalley/logs.js +307 -0
- package/dist/surfaces/stardewvalley/mods.js +182 -0
- package/dist/surfaces/stardewvalley/surface.js +244 -0
- package/dist/surfaces/stardewvalley/validators/content.js +321 -0
- package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
- package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
- package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
- package/dist/surfaces/subnautica2/compat.js +138 -0
- package/dist/surfaces/subnautica2/index.js +5 -0
- package/dist/surfaces/subnautica2/logs.js +129 -0
- package/dist/surfaces/subnautica2/paks.js +173 -0
- package/dist/surfaces/subnautica2/surface.js +151 -0
- package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
- package/dist/surfaces/valheim/compat.js +171 -0
- package/dist/surfaces/valheim/index/index.js +301 -0
- package/dist/surfaces/valheim/index/tables.js +103 -0
- package/dist/surfaces/valheim/index.js +11 -0
- package/dist/surfaces/valheim/logs.js +197 -0
- package/dist/surfaces/valheim/packaging.js +131 -0
- package/dist/surfaces/valheim/plugins.js +270 -0
- package/dist/surfaces/valheim/references.js +121 -0
- package/dist/surfaces/valheim/surface.js +223 -0
- package/dist/surfaces/valheim/validators/packaging.js +218 -0
- package/dist/surfaces/valheim/validators/plugins.js +287 -0
- package/dist/surfaces/valheim/validators/references.js +169 -0
- package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
- package/knowledge/baldursgate3/audio.voice.yaml +182 -0
- package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
- package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
- package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
- package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
- package/knowledge/baldursgate3/crash.causes.yaml +90 -0
- package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
- package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
- package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
- package/knowledge/baldursgate3/dialogue.yaml +122 -0
- package/knowledge/baldursgate3/feats.classes.yaml +164 -0
- package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
- package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
- package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
- package/knowledge/baldursgate3/journal.quests.yaml +214 -0
- package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
- package/knowledge/baldursgate3/localization.handles.yaml +92 -0
- package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
- package/knowledge/baldursgate3/origins.companions.yaml +144 -0
- package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
- package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
- package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
- package/knowledge/baldursgate3/process.launch.yaml +34 -0
- package/knowledge/baldursgate3/progressions.yaml +92 -0
- package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
- package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
- package/knowledge/baldursgate3/races.progression.yaml +228 -0
- package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
- package/knowledge/baldursgate3/scriptextender.yaml +252 -0
- package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
- package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
- package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
- package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
- package/knowledge/baldursgate3/stats.spells.yaml +293 -0
- package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
- package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
- package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
- package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
- package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
- package/knowledge/baldursgate3/ui.icons.yaml +412 -0
- package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
- package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
- package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
- package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
- package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
- package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
- package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
- package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
- package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
- package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
- package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
- package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
- package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
- package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
- package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
- package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
- package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
- package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
- package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
- package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
- package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
- package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
- package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
- package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
- package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
- package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
- package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
- package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
- package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
- package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
- package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
- package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
- package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
- package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
- package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
- package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
- package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
- package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
- package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
- package/knowledge/cyberpunk2077/player.control.yaml +918 -0
- package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
- package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
- package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
- package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
- package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
- package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
- package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
- package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
- package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
- package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
- package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
- package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
- package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
- package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
- package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
- package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
- package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
- package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
- package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
- package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
- package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
- package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
- package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
- package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
- package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
- package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
- package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
- package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
- package/knowledge/eldenring/formats.binders.yaml +237 -0
- package/knowledge/eldenring/launcher.me3.yaml +230 -0
- package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
- package/knowledge/eldenring/params.paramdef.yaml +149 -0
- package/knowledge/eldenring/params.regulation.yaml +142 -0
- package/knowledge/eldenring/saves.online.yaml +118 -0
- package/knowledge/eldenring/toolchain.cli.yaml +256 -0
- package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
- package/knowledge/skyrimse/archives.bsa.yaml +212 -0
- package/knowledge/skyrimse/install.detection.yaml +41 -0
- package/knowledge/skyrimse/loadorder.files.yaml +242 -0
- package/knowledge/skyrimse/plugins.esl.yaml +189 -0
- package/knowledge/skyrimse/plugins.format.yaml +224 -0
- package/knowledge/skyrimse/skse.runtime.yaml +283 -0
- package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
- package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
- package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
- package/knowledge/stardewvalley/game.versions.yaml +202 -0
- package/knowledge/stardewvalley/install.layout.yaml +277 -0
- package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
- package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
- package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
- package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
- package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
- package/knowledge/subnautica2/game.build.yaml +331 -0
- package/knowledge/subnautica2/modding.state.yaml +210 -0
- package/knowledge/subnautica2/ue5.containers.yaml +72 -0
- package/knowledge/subnautica2/ue5.paks.yaml +203 -0
- package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
- package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
- package/knowledge/valheim/bepinex.loader.yaml +247 -0
- package/knowledge/valheim/bepinex.plugins.yaml +251 -0
- package/knowledge/valheim/game.versions.yaml +321 -0
- package/knowledge/valheim/install.layout.yaml +216 -0
- package/knowledge/valheim/jotunn.library.yaml +334 -0
- package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
- package/knowledge/valheim/toolchain.build.yaml +111 -0
- package/package.json +29 -0
- package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
- package/scaffolds/baldursgate3/new-item.yaml +264 -0
- package/scaffolds/baldursgate3/new-mod.yaml +174 -0
- package/scaffolds/baldursgate3/new-passive.yaml +100 -0
- package/scaffolds/baldursgate3/new-project.yaml +118 -0
- package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
- package/scaffolds/baldursgate3/new-spell.yaml +102 -0
- package/scaffolds/baldursgate3/new-status.yaml +119 -0
- package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
- package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
- package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
- package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
- package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
- package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
- package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
- package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
- package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
- package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
- package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
- package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
- package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
- package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
- package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
- package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
- package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
- package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
- package/scaffolds/stardewvalley/new-project.yaml +184 -0
- package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
- package/scripts/mw-tool.mjs +184 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { compareVersions, parseVersion } from "../../../core/compat/version.js";
|
|
4
|
+
import { parseDependencyString, parseThunderstoreManifest } from "../packaging.js";
|
|
5
|
+
import { BEPINPLUGIN_GUID_RE, COMPATIBILITY_LEVELS, packageNameMatchesGuid, scanCfg, scanPluginSource, SEMVER_RE, SYSTEM_VERSION_RE, VERSION_STRICTNESS } from "../plugins.js";
|
|
6
|
+
import { PACKAGE_SOURCE_KIND } from "./packaging.js";
|
|
7
|
+
export const SOURCE_SOURCE_KIND = "source";
|
|
8
|
+
const PLUGIN_EVIDENCE = ["valheim.bepinex.plugins.bepinplugin-attribute-contract", "valheim.bepinex.plugins.valheim-pack-allows-semver-versions"];
|
|
9
|
+
const DEPENDENCY_EVIDENCE = ["valheim.bepinex.plugins.dependency-attributes", "valheim.bepinex.plugins.missing-hard-dependency-lines", "valheim.thunderstore.packaging.dependency-string-format", "valheim.toolchain.build.guid-to-package-name-has-no-registry"];
|
|
10
|
+
const HARMONY_EVIDENCE = ["valheim.bepinex.plugins.never-unpatchall-with-null", "valheim.bepinex.plugins.harmony-version-and-patch-shape"];
|
|
11
|
+
const NETWORK_EVIDENCE = ["valheim.jotunn.library.networkcompatibility-attribute"];
|
|
12
|
+
const CFG_EVIDENCE = ["valheim.bepinex.loader.bepinex-cfg-keys"];
|
|
13
|
+
function isWithin(parent, child) {
|
|
14
|
+
const rel = path.relative(parent, child);
|
|
15
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
16
|
+
}
|
|
17
|
+
function isCsUnderSource(target, project) {
|
|
18
|
+
if (target.kind !== "file" || path.extname(target.path).toLowerCase() !== ".cs")
|
|
19
|
+
return false;
|
|
20
|
+
return project.sources.some((s) => s.exists && s.kind === SOURCE_SOURCE_KIND && isWithin(s.absPath, target.path));
|
|
21
|
+
}
|
|
22
|
+
const CS_APPLIES = `every .cs file under a project source of kind "${SOURCE_SOURCE_KIND}"`;
|
|
23
|
+
async function scan(ctx, file) {
|
|
24
|
+
return scanPluginSource(await ctx.readText(file));
|
|
25
|
+
}
|
|
26
|
+
const bepInPlugin = {
|
|
27
|
+
id: "valheim.plugins.bepinplugin",
|
|
28
|
+
game: "valheim",
|
|
29
|
+
groups: ["plugins"],
|
|
30
|
+
modTypes: ["gameplay-tweak", "packaging"],
|
|
31
|
+
summary: "A class deriving from BaseUnityPlugin carries exactly one [BepInPlugin]; its GUID matches BepInEx's allowed pattern and its version parses (System.Version, or SemVer under the Valheim pack); a [BepInPlugin] on a class that is not a BaseUnityPlugin is ignored by the chainloader.",
|
|
32
|
+
appliesTo: isCsUnderSource,
|
|
33
|
+
appliesToDescription: CS_APPLIES,
|
|
34
|
+
needs: { project: true },
|
|
35
|
+
cost: "edit",
|
|
36
|
+
severity: "block",
|
|
37
|
+
evidence: PLUGIN_EVIDENCE,
|
|
38
|
+
async run(ctx) {
|
|
39
|
+
if (ctx.target.kind !== "file")
|
|
40
|
+
return [];
|
|
41
|
+
const file = ctx.target.path;
|
|
42
|
+
const name = path.basename(file);
|
|
43
|
+
const s = await scan(ctx, file);
|
|
44
|
+
const findings = [];
|
|
45
|
+
if (s.baseUnityPluginClasses.length > 0 && s.plugins.length === 0) {
|
|
46
|
+
for (const c of s.baseUnityPluginClasses) {
|
|
47
|
+
findings.push({ validatorId: this.id, severity: "block", file, line: c.line, message: `${c.name} in ${name} derives from BaseUnityPlugin but carries no [BepInPlugin(GUID, Name, Version)]; BepInEx ignores the type entirely.`, fix: "Add [BepInPlugin(\"com.example.mod\", \"Mod Name\", \"1.0.0\")] above the class.", evidence: PLUGIN_EVIDENCE });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (s.plugins.length > 1 && s.baseUnityPluginClasses.length <= 1) {
|
|
51
|
+
findings.push({ validatorId: this.id, severity: "block", file, line: s.plugins[1].line, message: `${name} carries ${s.plugins.length} [BepInPlugin] attributes for ${s.baseUnityPluginClasses.length} BaseUnityPlugin class${s.baseUnityPluginClasses.length === 1 ? "" : "es"}; each plugin class carries exactly one.`, evidence: PLUGIN_EVIDENCE });
|
|
52
|
+
}
|
|
53
|
+
if (s.plugins.length > 0 && s.baseUnityPluginClasses.length === 0) {
|
|
54
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: s.plugins[0].line, message: `${name} has a [BepInPlugin] but no class in this file derives from BaseUnityPlugin; the chainloader only loads BaseUnityPlugin subclasses, so if the class is declared here it will be skipped.`, evidence: PLUGIN_EVIDENCE });
|
|
55
|
+
}
|
|
56
|
+
for (const p of s.plugins) {
|
|
57
|
+
if (p.unresolved.length > 0) {
|
|
58
|
+
findings.push({ validatorId: this.id, severity: "info", file, line: p.line, message: `[BepInPlugin] in ${name} has argument${p.unresolved.length === 1 ? "" : "s"} ${p.unresolved.map((u) => `'${u}'`).join(", ")} not resolvable from this file (not a string literal or a same-file const string), so the GUID and version were not checked.`, evidence: PLUGIN_EVIDENCE });
|
|
59
|
+
}
|
|
60
|
+
if (p.guid !== undefined && !BEPINPLUGIN_GUID_RE.test(p.guid)) {
|
|
61
|
+
findings.push({ validatorId: this.id, severity: "block", file, line: p.line, message: `GUID "${p.guid}" in ${name} is not of BepInEx's allowed form (letters, digits, '.', '_', '-'); the chainloader logs "Skipping type ... because its GUID ... is of an illegal format" and never loads it.`, fix: "Use reverse-domain notation, e.g. com.author.modname.", evidence: PLUGIN_EVIDENCE });
|
|
62
|
+
}
|
|
63
|
+
if (p.version !== undefined && !SYSTEM_VERSION_RE.test(p.version)) {
|
|
64
|
+
if (SEMVER_RE.test(p.version)) {
|
|
65
|
+
findings.push({ validatorId: this.id, severity: "info", file, line: p.line, message: `Version "${p.version}" in ${name} is SemVer with a suffix: accepted by denikson's BepInExPack_Valheim, rejected by stock BepInEx 5 ("Skipping type ... because its version is invalid").`, evidence: PLUGIN_EVIDENCE });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
findings.push({ validatorId: this.id, severity: "block", file, line: p.line, message: `Version "${p.version}" in ${name} is neither a System.Version (2-4 integers) nor SemVer; BepInEx skips the plugin with "its version is invalid".`, evidence: PLUGIN_EVIDENCE });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (p.name !== undefined && p.name.trim().length === 0) {
|
|
72
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: p.line, message: `[BepInPlugin] in ${name} has an empty Name. BepInEx loads it — only a null name is skipped — but Name is the plugin's log source and its display name, so its LogOutput.log lines arrive as "[Info : ]" with nothing to attribute them to, and Jötunn's mismatch panel names it as blank.`, fix: 'Give it a human-readable name, e.g. [BepInPlugin("com.example.mod", "Mod Name", "1.0.0")].', evidence: PLUGIN_EVIDENCE });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return findings;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
async function manifestDependencies(ctx) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const s of ctx.project.sources) {
|
|
81
|
+
if (!s.exists || s.kind !== PACKAGE_SOURCE_KIND)
|
|
82
|
+
continue;
|
|
83
|
+
const file = path.join(s.absPath, "manifest.json");
|
|
84
|
+
let text;
|
|
85
|
+
try {
|
|
86
|
+
text = await fs.readFile(file, "utf8");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const { manifest } = parseThunderstoreManifest(text);
|
|
93
|
+
const packages = (manifest.dependencies ?? []).flatMap((raw) => {
|
|
94
|
+
const parsed = parseDependencyString(raw);
|
|
95
|
+
return parsed ? [{ ...parsed, raw }] : [];
|
|
96
|
+
});
|
|
97
|
+
out.push({ file, packages });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
const LOADER_PACKAGES = new Set(["bepinexpack_valheim", "bepinexpack"]);
|
|
105
|
+
const dependencyCrossCheck = {
|
|
106
|
+
id: "valheim.plugins.dependency-crosscheck",
|
|
107
|
+
game: "valheim",
|
|
108
|
+
groups: ["plugins"],
|
|
109
|
+
modTypes: ["gameplay-tweak", "packaging"],
|
|
110
|
+
summary: "Every hard [BepInDependency] GUID has a matching package in manifest.json's dependencies (by name), and every manifest dependency other than the loader pack is named by some [BepInDependency]; the managers install the manifest's list, BepInEx orders by the attribute's.",
|
|
111
|
+
appliesTo: (target, project) => isCsUnderSource(target, project) && project.sources.some((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND),
|
|
112
|
+
appliesToDescription: `${CS_APPLIES}, when the project also has a "${PACKAGE_SOURCE_KIND}" source`,
|
|
113
|
+
needs: { project: true },
|
|
114
|
+
cost: "edit",
|
|
115
|
+
severity: "warn",
|
|
116
|
+
evidence: DEPENDENCY_EVIDENCE,
|
|
117
|
+
async run(ctx) {
|
|
118
|
+
if (ctx.target.kind !== "file")
|
|
119
|
+
return [];
|
|
120
|
+
const file = ctx.target.path;
|
|
121
|
+
const name = path.basename(file);
|
|
122
|
+
const s = await scan(ctx, file);
|
|
123
|
+
if (s.dependencies.length === 0 && s.plugins.length === 0)
|
|
124
|
+
return [];
|
|
125
|
+
const manifests = await manifestDependencies(ctx);
|
|
126
|
+
if (manifests.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const findings = [];
|
|
129
|
+
const allPackages = manifests.flatMap((m) => m.packages.map((p) => ({ ...p, manifest: m.file })));
|
|
130
|
+
for (const d of s.dependencies) {
|
|
131
|
+
if (d.guid === undefined) {
|
|
132
|
+
if (d.unresolved.length > 0)
|
|
133
|
+
findings.push({ validatorId: this.id, severity: "info", file, line: d.line, message: `[BepInDependency] in ${name} names its GUID as '${d.unresolved[0]}', not resolvable from this file, so it was not cross-checked.`, evidence: DEPENDENCY_EVIDENCE });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (d.kind === "soft")
|
|
137
|
+
continue;
|
|
138
|
+
const match = allPackages.find((p) => packageNameMatchesGuid(p.name, d.guid));
|
|
139
|
+
if (!match) {
|
|
140
|
+
findings.push({
|
|
141
|
+
validatorId: this.id,
|
|
142
|
+
severity: "warn",
|
|
143
|
+
file,
|
|
144
|
+
line: d.line,
|
|
145
|
+
message: `${name} declares a hard [BepInDependency] on "${d.guid}", and no dependency in manifest.json (${allPackages.map((p) => p.raw).join(", ") || "none"}) has a package name matching it; if it is a Thunderstore package the managers will not install it, and BepInEx then logs "Could not load [...] because it has missing dependencies: ${d.guid}".`,
|
|
146
|
+
fix: "Add the package's {team}-{package}-{version} string to manifest.json's dependencies (or make the attribute a SoftDependency if the mod works without it). The match is by name and may miss a package whose name differs from its GUID.",
|
|
147
|
+
evidence: DEPENDENCY_EVIDENCE,
|
|
148
|
+
related: manifests.map((m) => m.file),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (d.minimumVersion !== undefined && parseVersion(d.minimumVersion) !== undefined && parseVersion(match.version) !== undefined) {
|
|
152
|
+
if (compareVersions(match.version, d.minimumVersion) < 0) {
|
|
153
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: d.line, message: `${name} requires "${d.guid}" at ${d.minimumVersion} or newer, but manifest.json pins ${match.raw}, which is older; the managers install the pinned version and BepInEx refuses the plugin.`, fix: `Raise the manifest dependency to ${match.team}-${match.name}-${d.minimumVersion} or lower the attribute's minimum.`, evidence: DEPENDENCY_EVIDENCE, related: [match.manifest] });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (s.plugins.length > 0) {
|
|
158
|
+
const guids = s.dependencies.map((d) => d.guid).filter((g) => g !== undefined);
|
|
159
|
+
for (const p of allPackages) {
|
|
160
|
+
if (LOADER_PACKAGES.has(p.name.toLowerCase()))
|
|
161
|
+
continue;
|
|
162
|
+
if (guids.some((g) => packageNameMatchesGuid(p.name, g)))
|
|
163
|
+
continue;
|
|
164
|
+
findings.push({
|
|
165
|
+
validatorId: this.id,
|
|
166
|
+
severity: "info",
|
|
167
|
+
file: p.manifest,
|
|
168
|
+
message: `manifest.json depends on ${p.raw}, and no [BepInDependency] in ${name} names a GUID matching "${p.name}"; the managers install it, but BepInEx will not order this plugin after it, so it may run before its dependency has loaded.`,
|
|
169
|
+
fix: `Add [BepInDependency("<its GUID>")] to the plugin class, or drop the manifest entry if it is not a plugin dependency. The match is by name and may miss a package whose name differs from its GUID.`,
|
|
170
|
+
evidence: DEPENDENCY_EVIDENCE,
|
|
171
|
+
related: [file],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return findings;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const harmony = {
|
|
179
|
+
id: "valheim.plugins.harmony-calls",
|
|
180
|
+
game: "valheim",
|
|
181
|
+
groups: ["plugins"],
|
|
182
|
+
modTypes: ["gameplay-tweak"],
|
|
183
|
+
summary: "UnpatchAll() with no id (or null) is never called: it removes every mod's patches and the community records save corruption from it. Parameterless PatchAll() is noted: its assembly comes from the calling stack frame, which inlining can get wrong.",
|
|
184
|
+
appliesTo: isCsUnderSource,
|
|
185
|
+
appliesToDescription: CS_APPLIES,
|
|
186
|
+
needs: { project: true },
|
|
187
|
+
cost: "edit",
|
|
188
|
+
severity: "block",
|
|
189
|
+
evidence: HARMONY_EVIDENCE,
|
|
190
|
+
async run(ctx) {
|
|
191
|
+
if (ctx.target.kind !== "file")
|
|
192
|
+
return [];
|
|
193
|
+
const file = ctx.target.path;
|
|
194
|
+
const name = path.basename(file);
|
|
195
|
+
const s = await scan(ctx, file);
|
|
196
|
+
const findings = [];
|
|
197
|
+
for (const u of s.unpatchAllWithoutId) {
|
|
198
|
+
findings.push({ validatorId: this.id, severity: "block", file, line: u.line, message: `${name} calls UnpatchAll() with no Harmony id: that removes every plugin's patches, BepInEx logs "UnpatchAll has been called - This will remove ALL HARMONY PATCHES.", and the Valheim wiki records save corruption from it.`, fix: 'Call harmony.UnpatchSelf() or UnpatchAll("your.harmony.id"), and not on game shutdown at all.', evidence: HARMONY_EVIDENCE });
|
|
199
|
+
}
|
|
200
|
+
for (const p of s.patchAllParameterless) {
|
|
201
|
+
findings.push({ validatorId: this.id, severity: "info", file, line: p.line, message: `${name} calls PatchAll() with no assembly; Harmony resolves it from the calling stack frame and its own documentation warns that inlining can pick the wrong one.`, fix: "Prefer PatchAll(Assembly.GetExecutingAssembly()) or Harmony.CreateAndPatchAll(typeof(Plugin).Assembly, id).", evidence: HARMONY_EVIDENCE });
|
|
202
|
+
}
|
|
203
|
+
return findings;
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const networkCompatibility = {
|
|
207
|
+
id: "valheim.plugins.network-compatibility",
|
|
208
|
+
game: "valheim",
|
|
209
|
+
groups: ["plugins"],
|
|
210
|
+
modTypes: ["gameplay-tweak"],
|
|
211
|
+
summary: `[NetworkCompatibility] names a CompatibilityLevel (${COMPATIBILITY_LEVELS.join(", ")}) and a VersionStrictness (${VERSION_STRICTNESS.join(", ")}); a mod enforcing EveryoneMustHaveMod or ServerMustHaveMod needs deploying to the server too.`,
|
|
212
|
+
appliesTo: isCsUnderSource,
|
|
213
|
+
appliesToDescription: CS_APPLIES,
|
|
214
|
+
needs: { project: true },
|
|
215
|
+
cost: "edit",
|
|
216
|
+
severity: "warn",
|
|
217
|
+
evidence: NETWORK_EVIDENCE,
|
|
218
|
+
async run(ctx) {
|
|
219
|
+
if (ctx.target.kind !== "file")
|
|
220
|
+
return [];
|
|
221
|
+
const file = ctx.target.path;
|
|
222
|
+
const name = path.basename(file);
|
|
223
|
+
const s = await scan(ctx, file);
|
|
224
|
+
const findings = [];
|
|
225
|
+
for (const n of s.networkCompatibility) {
|
|
226
|
+
if (n.level !== undefined && !COMPATIBILITY_LEVELS.includes(n.level)) {
|
|
227
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: n.line, message: `[NetworkCompatibility] in ${name} names CompatibilityLevel.${n.level}, which is not a member (${COMPATIBILITY_LEVELS.join(", ")}).`, evidence: NETWORK_EVIDENCE });
|
|
228
|
+
}
|
|
229
|
+
if (n.strictness !== undefined && !VERSION_STRICTNESS.includes(n.strictness)) {
|
|
230
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: n.line, message: `[NetworkCompatibility] in ${name} names VersionStrictness.${n.strictness}, which is not a member (${VERSION_STRICTNESS.join(", ")}).`, evidence: NETWORK_EVIDENCE });
|
|
231
|
+
}
|
|
232
|
+
if (n.level === "EveryoneMustHaveMod" || n.level === "ServerMustHaveMod") {
|
|
233
|
+
const clientOnly = s.bepInProcesses.some((p) => p.name?.toLowerCase() === "valheim.exe") && !s.bepInProcesses.some((p) => p.name?.toLowerCase() === "valheim_server.exe");
|
|
234
|
+
if (clientOnly) {
|
|
235
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: n.line, message: `${name} enforces CompatibilityLevel.${n.level} but its [BepInProcess] restricts it to valheim.exe, so it can never be loaded on the server it requires itself on; every client would be refused with Jötunn's "Missing Mods" panel.`, fix: "Drop the [BepInProcess] restriction or add one for valheim_server.exe.", evidence: NETWORK_EVIDENCE });
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
findings.push({ validatorId: this.id, severity: "info", file, line: n.line, message: `${name} enforces CompatibilityLevel.${n.level}: the dedicated server must run this exact plugin (to ${n.strictness ?? "the declared"} strictness) or clients are disconnected into Jötunn's mismatch panel; deploy it to the server install too.`, evidence: NETWORK_EVIDENCE });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return findings;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
function isCfgUnderSource(target, project) {
|
|
246
|
+
if (target.kind !== "file" || path.extname(target.path).toLowerCase() !== ".cfg")
|
|
247
|
+
return false;
|
|
248
|
+
return project.sources.some((s) => s.exists && isWithin(s.absPath, target.path));
|
|
249
|
+
}
|
|
250
|
+
const cfgSyntax = {
|
|
251
|
+
id: "valheim.plugins.config-syntax",
|
|
252
|
+
game: "valheim",
|
|
253
|
+
groups: ["plugins"],
|
|
254
|
+
modTypes: ["gameplay-tweak", "packaging"],
|
|
255
|
+
summary: "A shipped .cfg is in BepInEx's ConfigFile shape ([Section] headers, Key = Value, # comments) with no duplicate key in a section; a plugin config is named <GUID>.cfg.",
|
|
256
|
+
appliesTo: isCfgUnderSource,
|
|
257
|
+
appliesToDescription: "every .cfg under a project source",
|
|
258
|
+
needs: { project: true },
|
|
259
|
+
cost: "edit",
|
|
260
|
+
severity: "warn",
|
|
261
|
+
evidence: CFG_EVIDENCE,
|
|
262
|
+
async run(ctx) {
|
|
263
|
+
if (ctx.target.kind !== "file")
|
|
264
|
+
return [];
|
|
265
|
+
const file = ctx.target.path;
|
|
266
|
+
const name = path.basename(file);
|
|
267
|
+
const s = scanCfg(await ctx.readText(file));
|
|
268
|
+
const findings = [];
|
|
269
|
+
for (const m of s.malformed) {
|
|
270
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: m.line, message: `${name} line ${m.line} is neither a [Section], a Key = Value pair nor a # comment: "${m.text}"; BepInEx's ConfigFile skips it.`, evidence: CFG_EVIDENCE });
|
|
271
|
+
}
|
|
272
|
+
for (const d of s.duplicates) {
|
|
273
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: d.lines[d.lines.length - 1], message: `${name} sets "${d.key}" ${d.lines.length} times in [${d.section || "(no section)"}] (lines ${d.lines.join(", ")}); only one value survives.`, evidence: CFG_EVIDENCE });
|
|
274
|
+
}
|
|
275
|
+
if (s.entries.some((e) => e.section === "")) {
|
|
276
|
+
findings.push({ validatorId: this.id, severity: "warn", file, line: s.entries.find((e) => e.section === "").line, message: `${name} has Key = Value lines before any [Section]; BepInEx's ConfigFile keys every entry by section, so these belong to none.`, evidence: CFG_EVIDENCE });
|
|
277
|
+
}
|
|
278
|
+
if (name.toLowerCase() !== "bepinex.cfg") {
|
|
279
|
+
const guid = name.slice(0, -".cfg".length);
|
|
280
|
+
if (!BEPINPLUGIN_GUID_RE.test(guid) || !guid.includes(".")) {
|
|
281
|
+
findings.push({ validatorId: this.id, severity: "info", file, message: `${name} is not named like a plugin config: BepInEx writes a plugin's config as <GUID>.cfg (reverse-domain GUID), and a shipped file under config/ only takes effect under that name.`, evidence: CFG_EVIDENCE });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return findings;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
export const PLUGIN_VALIDATORS = [bepInPlugin, dependencyCrossCheck, harmony, networkCompatibility, cfgSyntax];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { walk } from "../../../core/fsutil.js";
|
|
3
|
+
import { scanReferences, translationKeys } from "../references.js";
|
|
4
|
+
import { SOURCE_SOURCE_KIND } from "./plugins.js";
|
|
5
|
+
const EVIDENCE = [
|
|
6
|
+
"valheim.jotunn.library.references-are-by-prefab-name-and-token",
|
|
7
|
+
"valheim.jotunn.library.jotunndoc-publishes-a-vanilla-index",
|
|
8
|
+
"valheim.jotunn.library.jotunndoc-table-shapes",
|
|
9
|
+
];
|
|
10
|
+
const APPLIES = `every .cs file under a project source of kind "${SOURCE_SOURCE_KIND}"`;
|
|
11
|
+
const MAX_PER_FILE = 30;
|
|
12
|
+
function isWithin(parent, child) {
|
|
13
|
+
const rel = path.relative(parent, child);
|
|
14
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
15
|
+
}
|
|
16
|
+
function isCsUnderSource(target, project) {
|
|
17
|
+
if (target.kind !== "file" || path.extname(target.path).toLowerCase() !== ".cs")
|
|
18
|
+
return false;
|
|
19
|
+
return project.sources.some((s) => s.exists && s.kind === SOURCE_SOURCE_KIND && isWithin(s.absPath, target.path));
|
|
20
|
+
}
|
|
21
|
+
async function projectDeclarations(ctx) {
|
|
22
|
+
const out = { prefabs: new Set(), tokens: new Set(), pieceTables: new Set() };
|
|
23
|
+
for (const s of ctx.project.sources) {
|
|
24
|
+
if (!s.exists)
|
|
25
|
+
continue;
|
|
26
|
+
const { files } = await walk(s.absPath);
|
|
27
|
+
for (const rel of files) {
|
|
28
|
+
const lower = rel.toLowerCase();
|
|
29
|
+
const full = path.join(s.absPath, ...rel.split("/"));
|
|
30
|
+
if (s.kind === SOURCE_SOURCE_KIND && lower.endsWith(".cs")) {
|
|
31
|
+
try {
|
|
32
|
+
const scan = scanReferences(await ctx.readText(full));
|
|
33
|
+
for (const p of scan.declares.prefabs)
|
|
34
|
+
out.prefabs.add(p.toLowerCase());
|
|
35
|
+
for (const t of scan.declares.tokens)
|
|
36
|
+
out.tokens.add(t.toLowerCase());
|
|
37
|
+
for (const t of scan.declares.pieceTables)
|
|
38
|
+
out.pieceTables.add(t.toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (lower.endsWith(".json") && /(^|\/)translations\//.test(lower)) {
|
|
44
|
+
try {
|
|
45
|
+
for (const k of translationKeys(await ctx.readText(full)))
|
|
46
|
+
out.tokens.add(k.toLowerCase());
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function declaredByProject(declared, ref) {
|
|
56
|
+
const value = ref.value.toLowerCase();
|
|
57
|
+
switch (ref.kind) {
|
|
58
|
+
case "token":
|
|
59
|
+
return declared.tokens.has(value.replace(/^\$/, ""));
|
|
60
|
+
case "prefab":
|
|
61
|
+
case "requirement":
|
|
62
|
+
return declared.prefabs.has(value);
|
|
63
|
+
case "piece_table":
|
|
64
|
+
return declared.pieceTables.has(value) || declared.prefabs.has(value);
|
|
65
|
+
case "asset":
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function inIndex(ctx, ref) {
|
|
70
|
+
switch (ref.kind) {
|
|
71
|
+
case "prefab":
|
|
72
|
+
case "requirement": {
|
|
73
|
+
const r = await ctx.lookup("entry", { name: ref.value, limit: 1 });
|
|
74
|
+
return r.count > 0;
|
|
75
|
+
}
|
|
76
|
+
case "piece_table": {
|
|
77
|
+
const byName = await ctx.lookup("entry", { kind: "piece_table", name: ref.value, limit: 1 });
|
|
78
|
+
if (byName.count > 0)
|
|
79
|
+
return true;
|
|
80
|
+
const tables = await ctx.lookup("entry", { kind: "piece_table", limit: 200 });
|
|
81
|
+
return tables.rows.some((row) => {
|
|
82
|
+
const data = row.data;
|
|
83
|
+
return data !== undefined && Object.entries(data).some(([column, value]) => /alias/i.test(column) && value.toLowerCase() === ref.value.toLowerCase());
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
case "token": {
|
|
87
|
+
const r = await ctx.lookup("token", { token: ref.value, limit: 1 });
|
|
88
|
+
return r.count > 0;
|
|
89
|
+
}
|
|
90
|
+
case "asset": {
|
|
91
|
+
const r = await ctx.lookup("asset", { id: ref.value });
|
|
92
|
+
return r.count > 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const KIND_WORDS = {
|
|
97
|
+
prefab: "prefab",
|
|
98
|
+
requirement: "recipe item",
|
|
99
|
+
piece_table: "piece table",
|
|
100
|
+
token: "localization token",
|
|
101
|
+
asset: "AssetID",
|
|
102
|
+
};
|
|
103
|
+
function consequence(ref) {
|
|
104
|
+
switch (ref.kind) {
|
|
105
|
+
case "prefab":
|
|
106
|
+
return "Jötunn's tutorials say a clone source must be an existing prefab; a name that resolves nowhere leaves the item or piece unregistered at runtime.";
|
|
107
|
+
case "requirement":
|
|
108
|
+
return "Jötunn's tutorials say a recipe's crafted item and its requirements must name existing item prefabs; a name that resolves nowhere makes the recipe unfulfillable or drops it when Jötunn resolves it.";
|
|
109
|
+
case "piece_table":
|
|
110
|
+
return "a piece whose table does not exist is never shown in any build menu.";
|
|
111
|
+
case "token":
|
|
112
|
+
return "Jötunn's localization falls back to English and then to Valheim's own Translation class, so a token neither the mod nor the game defines reaches the player unresolved.";
|
|
113
|
+
case "asset":
|
|
114
|
+
return "Iron Gate's soft-reference layer keys on the AssetID, which \"will always stay the same for each asset even if it's moved or renamed\", so an ID no indexed asset carries names nothing in the vanilla data.";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const references = {
|
|
118
|
+
id: "valheim.plugins.vanilla-references",
|
|
119
|
+
game: "valheim",
|
|
120
|
+
groups: ["plugins"],
|
|
121
|
+
modTypes: ["gameplay-tweak", "item", "weapon", "armor"],
|
|
122
|
+
summary: "Every prefab a CustomItem/CustomPiece clones or GetPrefab fetches, every recipe requirement item, every literal piece table, every $token and every 32-hex AssetID in the plugin's source resolves in the vanilla index or is declared by the project; each miss is a warning, since the name may belong to another mod or the dumps may lag the game.",
|
|
123
|
+
appliesTo: isCsUnderSource,
|
|
124
|
+
appliesToDescription: APPLIES,
|
|
125
|
+
needs: { project: true, index: true },
|
|
126
|
+
cost: "build",
|
|
127
|
+
severity: "warn",
|
|
128
|
+
evidence: EVIDENCE,
|
|
129
|
+
async run(ctx) {
|
|
130
|
+
if (ctx.target.kind !== "file")
|
|
131
|
+
return [];
|
|
132
|
+
const file = ctx.target.path;
|
|
133
|
+
const name = path.basename(file);
|
|
134
|
+
const scan = scanReferences(await ctx.readText(file));
|
|
135
|
+
if (scan.refs.length === 0)
|
|
136
|
+
return [];
|
|
137
|
+
const declared = await projectDeclarations(ctx);
|
|
138
|
+
const findings = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
const refs = [...scan.refs].sort((a, b) => a.line - b.line);
|
|
141
|
+
for (const [i, ref] of refs.entries()) {
|
|
142
|
+
const key = `${ref.kind}:${ref.value.toLowerCase()}`;
|
|
143
|
+
if (seen.has(key))
|
|
144
|
+
continue;
|
|
145
|
+
seen.add(key);
|
|
146
|
+
if (declaredByProject(declared, ref))
|
|
147
|
+
continue;
|
|
148
|
+
if (await inIndex(ctx, ref))
|
|
149
|
+
continue;
|
|
150
|
+
findings.push({
|
|
151
|
+
validatorId: this.id,
|
|
152
|
+
severity: "warn",
|
|
153
|
+
file,
|
|
154
|
+
line: ref.line,
|
|
155
|
+
message: `${name} references ${KIND_WORDS[ref.kind]} "${ref.value}" (${ref.via}), which is neither in the vanilla index nor declared by this project; ${consequence(ref)}`,
|
|
156
|
+
fix: ref.kind === "token"
|
|
157
|
+
? "Register the token with AddTranslation or a Translations/<Language>/*.json file, or use a vanilla token from the index (lookup kind token). The index carries the Token column of the JotunnDoc object lists, not Valheim's full translation table, so a vanilla token that belongs to no prefab, item or piece (a $menu_* or $msg_* string) warns here and is fine."
|
|
158
|
+
: "Check the spelling against the index (lookup kind entry, or asset), or declare the name in this project; if it belongs to another installed mod, this warning is expected.",
|
|
159
|
+
evidence: EVIDENCE,
|
|
160
|
+
});
|
|
161
|
+
if (findings.length >= MAX_PER_FILE && i < refs.length - 1) {
|
|
162
|
+
findings.push({ validatorId: this.id, severity: "info", file, message: `More unresolved references in ${name} than this rule lists (${MAX_PER_FILE}); fix these first.`, evidence: EVIDENCE });
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return findings;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
export const REFERENCE_VALIDATORS = [references];
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
game: baldursgate3
|
|
2
|
+
topic: animations.gr2
|
|
3
|
+
title: GR2 animation export (GLTF vs Collada) and Toolkit animation registration
|
|
4
|
+
facts:
|
|
5
|
+
- id: bg3.animations.gr2.export-via-gltf-not-collada-for-animations
|
|
6
|
+
claim: When exporting a baked skeleton animation to .GR2 with Norbyte's DOS2/BG3 Collada Exporter
|
|
7
|
+
add-on, the documented path is "export via GLTF" specifically, "not via Collada" — the add-on
|
|
8
|
+
supports both, but the GLTF path is the one that correctly carries animation-bearing meshes
|
|
9
|
+
for BG3.
|
|
10
|
+
status: community
|
|
11
|
+
source: wiki.bg3.community/en/Tutorials/Animations/CustomAnimationsBlender §How to correctly bake
|
|
12
|
+
and export your animation
|
|
13
|
+
tags:
|
|
14
|
+
- animation
|
|
15
|
+
- gr2
|
|
16
|
+
- gltf
|
|
17
|
+
- collada
|
|
18
|
+
- export
|
|
19
|
+
- id: bg3.animations.gr2.export-only-skeleton-not-control-rig-or-meshes
|
|
20
|
+
claim: "\"You only want to export the skeleton rig, not the control rig or any of the meshes\",
|
|
21
|
+
because \"While you do your animating on the control rig, the skeleton does not receive any
|
|
22
|
+
keyframes, it's being constrained (puppeteered) by the control rig\" — so the Action must
|
|
23
|
+
first be baked onto the skeleton (\"Go into Pose > Animation > Bake Action\") before export."
|
|
24
|
+
status: community
|
|
25
|
+
source: wiki.bg3.community/en/Tutorials/Animations/CustomAnimationsBlender §How to correctly bake
|
|
26
|
+
and export your animation
|
|
27
|
+
tags:
|
|
28
|
+
- animation
|
|
29
|
+
- gr2
|
|
30
|
+
- bake-action
|
|
31
|
+
- control-rig
|
|
32
|
+
- export
|
|
33
|
+
- id: bg3.animations.gr2.bake-action-clear-constraints-disconnects-rig
|
|
34
|
+
claim: Baking Action on a skeleton rig with "clear constraints" checked disconnects the skeleton
|
|
35
|
+
from the control rig afterward (moving the control rig no longer moves the skeleton); leaving
|
|
36
|
+
that option unchecked keeps the skeleton connected to the control rig after baking.
|
|
37
|
+
status: community
|
|
38
|
+
source: wiki.bg3.community/en/Tutorials/Animations/CustomAnimationsBlender §How to correctly bake
|
|
39
|
+
and export your animation
|
|
40
|
+
tags:
|
|
41
|
+
- animation
|
|
42
|
+
- bake-action
|
|
43
|
+
- control-rig
|
|
44
|
+
- id: bg3.animations.gr2.gr2-bones-are-joints-not-blender-bones
|
|
45
|
+
claim: GR2 bones function as single-point joints (like Maya), not two-ended Blender bones with a
|
|
46
|
+
head and tail; Blender's importer guesses a tail position using Blender's own primary axis
|
|
47
|
+
convention (Y) versus GR2/Maya's (X), which makes imported bones look "sideways" in Blender —
|
|
48
|
+
this is expected and exports back correctly, since only the joint/bone head position actually
|
|
49
|
+
matters.
|
|
50
|
+
status: community
|
|
51
|
+
source: wiki.bg3.community/en/Tutorials/Animations/CustomAnimationsBlender §How to correctly bake
|
|
52
|
+
and export your animation
|
|
53
|
+
tags:
|
|
54
|
+
- animation
|
|
55
|
+
- gr2
|
|
56
|
+
- bones
|
|
57
|
+
- joints
|
|
58
|
+
- blender
|
|
59
|
+
- id: bg3.animations.gr2.filename-identity-required-between-custom-and-toolkit-gr2
|
|
60
|
+
claim: "\"It's important that your custom animation .GR2 is named the same as the file in the
|
|
61
|
+
Toolkit\": you open the animation resource's \"Source File\" path (minus the filename), then
|
|
62
|
+
\"replace the vanilla .GR2 file with your own in the folder you just opened up\"."
|
|
63
|
+
status: community
|
|
64
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §Adding Poses to the
|
|
65
|
+
_Base
|
|
66
|
+
tags:
|
|
67
|
+
- animation
|
|
68
|
+
- gr2
|
|
69
|
+
- toolkit
|
|
70
|
+
- filename-identity
|
|
71
|
+
- silent-failure
|
|
72
|
+
- id: bg3.animations.gr2.body-type-base-fallback-to-hum-m-base
|
|
73
|
+
claim: If a specific body-type _Base AnimationSetResource doesn't have an animation registered, the
|
|
74
|
+
game falls back to whatever is registered in HUM_M_Base — "everyone gets to use the HUM_M_Base
|
|
75
|
+
ones" — so an animation added only to HUM_F_Base is usable only by BT1 (that body type)
|
|
76
|
+
characters, while one added to HUM_M_Base is available to all body types that don't have their
|
|
77
|
+
own override.
|
|
78
|
+
status: community
|
|
79
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §Adding Poses to the
|
|
80
|
+
_Base ("Everything will default back to the HUM_M_Base if the other bases don't have an
|
|
81
|
+
animation set into them, (everyone gets to use the HUM_M_Base ones) if something is just in
|
|
82
|
+
the HUM_F_Base then only the BT1 characters can use those")
|
|
83
|
+
tags:
|
|
84
|
+
- animation
|
|
85
|
+
- animationset
|
|
86
|
+
- hum-m-base
|
|
87
|
+
- fallback
|
|
88
|
+
- id: bg3.animations.gr2.photomode-pose-mods-override-each-other
|
|
89
|
+
claim: Photomode pose/animation mods override one another rather than stacking, so only one such mod
|
|
90
|
+
can be active in a load order at a time — a constraint worth stating explicitly when
|
|
91
|
+
publishing.
|
|
92
|
+
status: community
|
|
93
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit ("Currently,
|
|
94
|
+
photomode pose/animation mods override one another, so you can only have one in your load
|
|
95
|
+
order at a time")
|
|
96
|
+
tags:
|
|
97
|
+
- animation
|
|
98
|
+
- photomode
|
|
99
|
+
- load-order
|
|
100
|
+
- compatibility
|
|
101
|
+
- id: bg3.animations.gr2.larian-packs-multiple-poses-per-animation-file
|
|
102
|
+
claim: '"Instead of having one animation file for each static pose, Larian puts each pose in a
|
|
103
|
+
collection into one animation file. The timing for each pose is then set in the Object UUID
|
|
104
|
+
Editor."'
|
|
105
|
+
status: community
|
|
106
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §2.4 EmoteCollections
|
|
107
|
+
tags:
|
|
108
|
+
- animation
|
|
109
|
+
- toolkit
|
|
110
|
+
- pose-collection
|
|
111
|
+
- id: bg3.animations.gr2.shortname-required-per-individual-pose
|
|
112
|
+
claim: In the Object UUID Editor's ShortNames, "you want to add an entry for each pose that you
|
|
113
|
+
have. Not for a collection/pack, but for every single animation" — so a file holding 10 static
|
|
114
|
+
poses needs 10 ShortName entries. One pose adjusted per body type can reuse a single ShortName
|
|
115
|
+
across each body-type's _Base ("HUM_F_Hugs_BT1 and the same pose adjusted for hight for
|
|
116
|
+
HUM_M_Hugs_BT1 can share the same shortname").
|
|
117
|
+
status: community
|
|
118
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §2.2 ShortNames
|
|
119
|
+
tags:
|
|
120
|
+
- animation
|
|
121
|
+
- shortnames
|
|
122
|
+
- toolkit
|
|
123
|
+
- uuid-object-editor
|
|
124
|
+
- id: bg3.animations.gr2.emoteposes-vs-emoteanimations-distinction
|
|
125
|
+
claim: A static pose is registered under EmotePoses; a moving animation follows "these same steps
|
|
126
|
+
but instead of adding 'EmotePoses', add 'EmoteAnimations' instead. The only difference is that
|
|
127
|
+
is doesn't have 'Timing' in the tab."
|
|
128
|
+
status: community
|
|
129
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §2.3 EmotePoses and
|
|
130
|
+
EmoteAnimations
|
|
131
|
+
tags:
|
|
132
|
+
- animation
|
|
133
|
+
- emoteposes
|
|
134
|
+
- emoteanimations
|
|
135
|
+
- toolkit
|
|
136
|
+
- id: bg3.animations.gr2.toolkit-restart-needed-after-gr2-replacement
|
|
137
|
+
claim: After manually replacing a vanilla .GR2 file on disk with a custom one of the same name, the
|
|
138
|
+
Toolkit must be closed and reopened for the new animation to show correctly in the project
|
|
139
|
+
folder — it will not pick up the swapped file while still running.
|
|
140
|
+
status: community
|
|
141
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit ("you have to close
|
|
142
|
+
down the Toolkit and open it again in order for your animations to show properly in the
|
|
143
|
+
project folder")
|
|
144
|
+
tags:
|
|
145
|
+
- animation
|
|
146
|
+
- toolkit
|
|
147
|
+
- gr2
|
|
148
|
+
- restart-required
|
|
149
|
+
- id: bg3.animations.gr2.publish-local-required-before-bg3mm-load-order
|
|
150
|
+
claim: After setting up an animation mod's project settings (Project > Project Settings, Ctrl+P) and
|
|
151
|
+
filling the mandatory fields plus author, the Toolkit's "Publish Local" step is what generates
|
|
152
|
+
the .pak into the local Mods folder, which is then added to a load order and exported to the
|
|
153
|
+
game in BG3 Mod Manager — this is a Toolkit-only publish step distinct from publishing to
|
|
154
|
+
mod.io.
|
|
155
|
+
status: community
|
|
156
|
+
source: wiki.bg3.community/en/Tutorials/Animations/Adding-Animations-In-Toolkit §Exporting Your Mod
|
|
157
|
+
("Publish Local (automatically adds the mod to your Mods folder)")
|
|
158
|
+
tags:
|
|
159
|
+
- animation
|
|
160
|
+
- toolkit
|
|
161
|
+
- publish-local
|
|
162
|
+
- bg3mm
|
|
163
|
+
detail: Larian's own publishing doc describes Publish Local as writing a .pak "without uploading it
|
|
164
|
+
to mod.io" to a location you choose — see
|
|
165
|
+
bg3.publishing.toolkit-modio.publish-local-vs-publish-to-modio; this page's phrasing is the
|
|
166
|
+
animation workflow's shorthand for the same button.
|