ff9mapkit 1.0.0b4__tar.gz → 1.0.0b6__tar.gz
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.
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/PKG-INFO +1 -1
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/__init__.py +1 -1
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/build.py +14 -3
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/config.py +120 -16
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/jobs.py +7 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/extract.py +15 -5
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/builddoc.py +41 -4
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/importdoc.py +6 -2
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/PKG-INFO +1 -1
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/pyproject.toml +1 -1
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_ate.py +17 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_setup.py +50 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_verbatim.py +59 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/LICENSE +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/README.md +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/__main__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_animdb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_animdb_all.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_fieldtable.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_fieldtext.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_held_poses.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_itemdb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_modeldb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_narrowmap_data.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_npcparams.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_animdb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_animdb_all.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_fieldtable.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_fieldtext.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_modeldb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_npcparams.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_regen_scenedb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/_scenedb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/abilities.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/animations.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/archetypes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/areatitle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/abilityfeatures.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/actiondelta.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/aiauthor.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/ailint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/aipatch.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/battleai.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/battlecsv.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/battlepatch.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/build.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/camera_codec.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/camera_data.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/characterdelta.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/event_data.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/extract.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/fbx.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/reskin.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/scene_codec.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/scene_data.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/scenelint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/seqasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/seqauthor.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/seqcodec.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/seqdis.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle/seqpatch.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/battle_bgm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/binutils.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/campaign.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/catalog.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/chain.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/cli.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/areatitle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/ate.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/camera.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/chest.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/choice.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/conductor.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/cutscene.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/encounter.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/entry_settle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/equipment.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/event.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/gateway.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/inventory.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/itemdata.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/itemtext.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/jump.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/ladder.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/movement.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/music.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/npc.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/object.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/onentry.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/party.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/pathfind.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/platform.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/player.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/prop.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/region.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/reinit.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/savepoint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/shop.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/sps_trigger.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/startup.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/synthesis.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/text.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/textcarry.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/verbatim.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/content/walkmesh_hotfix.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/_regen_provenance.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.es.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.fr.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.gr.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.it.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.jp.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.uk.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/blank.us.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/manifest.json +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/provenance/region_template.patch +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/reference_arcs.toml +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/data/region_catalog.toml +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/deploystack.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/dialogue.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/_exprtable.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/_membertable.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/_optables.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/_regen_optables.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/cmdasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/disasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/edit.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/exprasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/model.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eb/opcodes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eblint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/app.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/battle_forms.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/breadcrumb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/dialogs.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/feedback.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/forms.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/graphview.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/model.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/picker.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/editor/theme.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/eventscan.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/flags.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/forkreport.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/hub.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/idgated.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/infohub.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/items.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/itemstats.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/journey.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/keyitems.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/logic_add.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/logic_edit.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/logic_map.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/memoria.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/pack.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/playerswap.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/prop_archetypes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/provision.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/refarc.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/save.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/save_items.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/arena.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/bgart.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/bgi.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/bgs.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/bgx.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/cam.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/guide.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/paint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/scene/placeholder.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sjbinary.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/author.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/catalog.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/codec.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/edit.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/lint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/render.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/templates.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/sps/texture.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/walkmesh_hotfixes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/__init__.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/battledoc.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/forms_qt.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/mapview.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/palette.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/savedoc.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/shell.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/style.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit/workspace/tuningdialog.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/SOURCES.txt +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/dependency_links.txt +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/entry_points.txt +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/requires.txt +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/ff9mapkit.egg-info/top_level.txt +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/setup.cfg +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_abilities.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_abilityfeatures.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_actiondelta.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_ai_phase_insert.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_ai_phase_insert_adversary.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_aiauthor.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_ailint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_aipatch.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_animations.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_archetypes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_areatitle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_arming.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battle_bgm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battle_forms.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battle_scene_codec.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battle_seq.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battleai.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battlecsv.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_battlepatch.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_bgart.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_bgs.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_build.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_cameras.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_campaign.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_capstone.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_carry_text_lint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_catalog.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_chain.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_characterdelta.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_choice.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_cli_entry.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_cmdasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_cmdasm_relocate.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_content.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_deploy_campaign.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_deploystack.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_dialogue.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_eb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_eblint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_app.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_breadcrumb.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_feedback.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_forms.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_integration.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_jobs.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_model.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_editor_theme.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_entry_settle.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_eventscan.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_export.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_exprasm.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_extract_area.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_find_field.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_flags.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_forkreport.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_gateway_advance.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_graphview.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_hub_gen.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_idgated.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_import_borrow.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_infohub.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_itemdata.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_items.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_itemstats.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_itemtext.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_journey.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_journey_merge.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_jump.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_ladder.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_lint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_logic_add.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_logic_edit.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_logic_map.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_movement.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_npc_model.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_npc_verbatim.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_npcparams.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_object_graft.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_occlusion.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_on_entry.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_pack.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_paint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_party.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_platform.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_player_graft.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_playerswap.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_prop_archetypes.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_provision.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_refarc.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_repaint_native.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_reskin.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_save.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_save_items.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_savepoint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_scene.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_scenelint.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_scroll.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_shared_text_block.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_shop.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_showcase.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_sjbinary.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_spawn.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_sps.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_startstate.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_startup.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_synthesis.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_text.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_textcarry.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_walkmesh_hotfix.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_workspace_style.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_world_hub.py +0 -0
- {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b6}/tests/test_yaw_movement.py +0 -0
|
@@ -15,4 +15,4 @@ Public surface is organized as:
|
|
|
15
15
|
ff9mapkit.battle — the battle.toml -> custom battle-background (BBG) builder (fork/edit/build a 3D battle map)
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "1.0.
|
|
18
|
+
__version__ = "1.0.0b6" # keep in lockstep with [project] version in pyproject.toml
|
|
@@ -1839,6 +1839,16 @@ def lint_logic(project: FieldProject) -> list[str]:
|
|
|
1839
1839
|
for ln in _text.overflow_lines(t, wrap):
|
|
1840
1840
|
out.append(f"{who} has a word too wide to fit one line ({ln!r}) -- it will overflow; "
|
|
1841
1841
|
f"shorten it or raise [dialogue] wrap.")
|
|
1842
|
+
# DEPRECATION: `[cutscene] ate = true` is the OLD, unfaithful forced-ATE model -- a grey banner held over
|
|
1843
|
+
# THIS in-place cutscene. A real grey ATE WARPS you to a dedicated scene and back (verified vs 6 real grey
|
|
1844
|
+
# ATEs; see project-ff9-ate-system). Steer to the faithful trigger; the held-banner still builds (not an error).
|
|
1845
|
+
_cs = raw.get("cutscene")
|
|
1846
|
+
if isinstance(_cs, dict) and _cs.get("ate"):
|
|
1847
|
+
out.append("[cutscene] ate = true is the OLD held-banner ATE styling (a grey banner over this in-place "
|
|
1848
|
+
"cutscene) -- NOT how a real grey ATE works (it WARPS you to a scene, plays it, and warps you "
|
|
1849
|
+
"back). For a faithful forced ATE use `[[gateway]] ate = true` (the warp-in trigger: banner "
|
|
1850
|
+
"warning + centered title window, then the warp) + a plain [cutscene] + exit_warp on the "
|
|
1851
|
+
"destination field. This held-banner flavor still builds, but it isn't faithful.")
|
|
1842
1852
|
|
|
1843
1853
|
# reference-data sanity (Info Hub): an [[npc]] model id / animation id the engine won't recognise.
|
|
1844
1854
|
# A model NAME is handled by validate() (fatal); here we WARN on a raw id outside the known tables
|
|
@@ -3183,9 +3193,10 @@ _UID_HOTFIX_DONORS = frozenset((900, 2803))
|
|
|
3183
3193
|
|
|
3184
3194
|
|
|
3185
3195
|
def _verbatim_donor_id(project: FieldProject):
|
|
3186
|
-
"""Best-effort donor field id of
|
|
3187
|
-
records it as ``[verbatim_eb] donor
|
|
3188
|
-
|
|
3196
|
+
"""Best-effort donor field id of ANY fork (verbatim OR native/synth), for engine-hotfix warnings AND the
|
|
3197
|
+
deploy-time ForkDonorPatch `<forkId> <donorId>` mapping. The import records it as ``[verbatim_eb] donor``
|
|
3198
|
+
(verbatim) or ``[field] source_field`` (native/synth); ``borrow_field`` is the BG-borrow form. ``None``
|
|
3199
|
+
when an older fork's toml lacks it (the warn/emit is then skipped -- pre-record forks need a hand-added line)."""
|
|
3189
3200
|
for blk, key in (("verbatim_eb", "donor"), ("field", "source_field"), ("field", "borrow_field")):
|
|
3190
3201
|
v = (project.raw.get(blk) or {}).get(key)
|
|
3191
3202
|
if isinstance(v, bool):
|
|
@@ -46,6 +46,15 @@ _COMMON_STEAM_PATHS = (
|
|
|
46
46
|
r"D:\SteamLibrary\steamapps\common\FINAL FANTASY IX",
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
+
# FF9 store identifiers -- used to read the per-game install path from the Windows registry (the SAME keys
|
|
50
|
+
# Memoria's patcher reads, so we resolve exactly the folder it patches -- correct even on a secondary drive
|
|
51
|
+
# / custom Steam library). Steam + GOG are the same Unity port with an identical on-disk layout; the
|
|
52
|
+
# Microsoft Store / Xbox Game Pass build is a different, non-Unity, DRM-locked package Memoria can't patch,
|
|
53
|
+
# so the detector never targets it. Refs: Albeoris/Memoria Memoria.Patcher/GameInfo/.
|
|
54
|
+
_STEAM_APPID = "377840"
|
|
55
|
+
_GOG_GAMEID = "1375008492"
|
|
56
|
+
_FF9_DIRNAME = "FINAL FANTASY IX"
|
|
57
|
+
|
|
49
58
|
|
|
50
59
|
class ConfigError(RuntimeError):
|
|
51
60
|
"""Raised when a required path cannot be resolved or does not exist."""
|
|
@@ -80,34 +89,129 @@ def save_game_path(game_path: str | os.PathLike) -> Path:
|
|
|
80
89
|
return USER_CONFIG
|
|
81
90
|
|
|
82
91
|
|
|
92
|
+
def _is_ff9_root(p: Path) -> bool:
|
|
93
|
+
"""True if ``p`` is a real, MODDABLE FF9 install (Steam or GOG -- same layout). Keys off Memoria's own
|
|
94
|
+
sentinel (``FF9_Launcher.exe``) plus ``StreamingAssets`` and the managed-DLL tree -- which also rejects
|
|
95
|
+
the un-moddable Microsoft Store build (no launcher / no Managed dir) and stale empty folders."""
|
|
96
|
+
try:
|
|
97
|
+
return ((p / "FF9_Launcher.exe").is_file()
|
|
98
|
+
and (p / "StreamingAssets").is_dir()
|
|
99
|
+
and ((p / "x64" / "FF9_Data" / "Managed").is_dir()
|
|
100
|
+
or (p / "x86" / "FF9_Data" / "Managed").is_dir()))
|
|
101
|
+
except OSError:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _reg_str(hive, subkey: str, value: str) -> str | None:
|
|
106
|
+
"""Read one registry string value, trying the 64-bit THEN 32-bit view (FF9's keys live under
|
|
107
|
+
``WOW6432Node`` on 64-bit Windows). Windows-only; returns None elsewhere or if the key/value is absent."""
|
|
108
|
+
if os.name != "nt":
|
|
109
|
+
return None
|
|
110
|
+
import winreg # noqa: PLC0415 - Windows-only, lazy
|
|
111
|
+
for view in (winreg.KEY_WOW64_64KEY, winreg.KEY_WOW64_32KEY):
|
|
112
|
+
try:
|
|
113
|
+
with winreg.OpenKeyEx(hive, subkey, 0, winreg.KEY_READ | view) as key:
|
|
114
|
+
data, _ = winreg.QueryValueEx(key, value)
|
|
115
|
+
if data:
|
|
116
|
+
return str(data)
|
|
117
|
+
except OSError:
|
|
118
|
+
continue
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _registry_candidates() -> list[Path]:
|
|
123
|
+
"""FF9 roots from the per-game Steam + GOG registry keys (the exact keys Memoria reads). These point at
|
|
124
|
+
the ACTUAL install dir, so they find FF9 even on a secondary drive or a custom-named Steam library."""
|
|
125
|
+
if os.name != "nt":
|
|
126
|
+
return []
|
|
127
|
+
import winreg # noqa: PLC0415
|
|
128
|
+
out: list[Path] = []
|
|
129
|
+
steam = _reg_str(winreg.HKEY_LOCAL_MACHINE,
|
|
130
|
+
rf"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App {_STEAM_APPID}",
|
|
131
|
+
"InstallLocation")
|
|
132
|
+
if steam:
|
|
133
|
+
out.append(Path(steam))
|
|
134
|
+
gog = _reg_str(winreg.HKEY_LOCAL_MACHINE, rf"SOFTWARE\GOG.com\Games\{_GOG_GAMEID}", "path")
|
|
135
|
+
if gog:
|
|
136
|
+
out.append(Path(gog))
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _parse_vdf_library_paths(text: str) -> list[str]:
|
|
141
|
+
"""Library-folder paths from a Steam ``libraryfolders.vdf`` -- handles both the old ``"1" "<path>"`` and
|
|
142
|
+
the new ``"path" "<path>"`` schemas (and ignores the new schema's ``"apps"`` appid->buildid map). The
|
|
143
|
+
file doubles its backslashes, so they're unescaped here."""
|
|
144
|
+
keyed = re.findall(r'"path"\s+"([^"]+)"', text) if '"path"' in text \
|
|
145
|
+
else re.findall(r'"\d+"\s+"([^"]+)"', text)
|
|
146
|
+
return [p.replace("\\\\", "\\") for p in keyed]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _steam_library_candidates() -> list[Path]:
|
|
150
|
+
"""FF9 under every Steam library (a fallback when the per-game Uninstall key is missing): locate Steam
|
|
151
|
+
via the registry, parse ``libraryfolders.vdf``, and join ``steamapps/common/FINAL FANTASY IX``."""
|
|
152
|
+
if os.name != "nt":
|
|
153
|
+
return []
|
|
154
|
+
import winreg # noqa: PLC0415
|
|
155
|
+
steam = (_reg_str(winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam", "SteamPath")
|
|
156
|
+
or _reg_str(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath"))
|
|
157
|
+
if not steam:
|
|
158
|
+
return []
|
|
159
|
+
steam_dir = Path(steam)
|
|
160
|
+
out: list[Path] = [steam_dir / "steamapps" / "common" / _FF9_DIRNAME]
|
|
161
|
+
for vdf in (steam_dir / "steamapps" / "libraryfolders.vdf", steam_dir / "config" / "libraryfolders.vdf"):
|
|
162
|
+
try:
|
|
163
|
+
text = vdf.read_text(encoding="utf-8", errors="ignore")
|
|
164
|
+
except OSError:
|
|
165
|
+
continue
|
|
166
|
+
for lib in _parse_vdf_library_paths(text):
|
|
167
|
+
out.append(Path(lib) / "steamapps" / "common" / _FF9_DIRNAME)
|
|
168
|
+
return out
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _fallback_candidates() -> list[Path]:
|
|
172
|
+
"""Last-resort default install folders (Steam + GOG) if registry/library lookups don't resolve."""
|
|
173
|
+
gog = [Path(b) / _FF9_DIRNAME for b in
|
|
174
|
+
(r"C:\GOG Games", r"D:\GOG Games", r"C:\Program Files (x86)\GOG Galaxy\Games")]
|
|
175
|
+
return [Path(p) for p in _COMMON_STEAM_PATHS] + gog
|
|
176
|
+
|
|
177
|
+
|
|
83
178
|
def find_game_path(explicit: str | os.PathLike | None = None) -> Path:
|
|
84
179
|
"""Resolve the Final Fantasy IX install folder.
|
|
85
180
|
|
|
86
|
-
Order: explicit arg >
|
|
87
|
-
|
|
181
|
+
Order: explicit arg > ``$FF9_GAME_PATH`` > ``~/.ff9mapkit.toml`` (``game_path``) > auto-detect. The
|
|
182
|
+
auto-detector mirrors Memoria's: the per-game Steam + GOG registry keys, then a Steam-library scan, then
|
|
183
|
+
the common default folders. Explicit/env/config paths are trusted if they exist; auto-detected ones are
|
|
184
|
+
validated as a real, moddable FF9 install (which also skips the un-moddable Microsoft Store build).
|
|
185
|
+
Raises ConfigError with actionable guidance if nothing resolves.
|
|
88
186
|
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
env = os.environ.get("FF9_GAME_PATH")
|
|
93
|
-
if env:
|
|
94
|
-
candidates.append(Path(env))
|
|
95
|
-
cfg = _read_user_config().get("game_path")
|
|
96
|
-
if cfg:
|
|
97
|
-
candidates.append(Path(cfg))
|
|
98
|
-
candidates.extend(Path(p) for p in _COMMON_STEAM_PATHS)
|
|
99
|
-
|
|
100
|
-
for c in candidates:
|
|
187
|
+
# 1) user-specified -- trusted on existence
|
|
188
|
+
for c in (Path(p) for p in (explicit, os.environ.get("FF9_GAME_PATH"),
|
|
189
|
+
_read_user_config().get("game_path")) if p):
|
|
101
190
|
if c.is_dir():
|
|
102
191
|
return c.resolve()
|
|
103
192
|
|
|
193
|
+
# 2) auto-detect Steam + GOG, validated as a real install; de-dupe by resolved path
|
|
194
|
+
seen: set[Path] = set()
|
|
195
|
+
for c in _registry_candidates() + _steam_library_candidates() + _fallback_candidates():
|
|
196
|
+
try:
|
|
197
|
+
rc = c.resolve()
|
|
198
|
+
except OSError:
|
|
199
|
+
continue
|
|
200
|
+
if rc in seen:
|
|
201
|
+
continue
|
|
202
|
+
seen.add(rc)
|
|
203
|
+
if _is_ff9_root(c):
|
|
204
|
+
return rc
|
|
205
|
+
|
|
104
206
|
raise ConfigError(
|
|
105
|
-
"Could not locate the Final Fantasy IX install folder
|
|
207
|
+
"Could not locate the Final Fantasy IX install folder (checked Steam + GOG via the registry,\n"
|
|
208
|
+
"the Steam libraries, and the common install paths).\n"
|
|
106
209
|
"Set it one of these ways:\n"
|
|
107
210
|
" - pass --game \"<path>\" on the command line\n"
|
|
108
211
|
" - export FF9_GAME_PATH=\"<path>\"\n"
|
|
109
212
|
f" - add game_path = \"<path>\" to {USER_CONFIG}\n"
|
|
110
|
-
"The folder should contain FF9_Launcher.exe and a StreamingAssets directory
|
|
213
|
+
"The folder should contain FF9_Launcher.exe and a StreamingAssets directory.\n"
|
|
214
|
+
"(The Microsoft Store / Xbox Game Pass version is not moddable -- use the Steam or GOG release.)"
|
|
111
215
|
)
|
|
112
216
|
|
|
113
217
|
|
|
@@ -80,6 +80,13 @@ def detect_deploy_target(repo_root):
|
|
|
80
80
|
return mod, fid
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def has_deploy_tools(repo_root) -> bool:
|
|
84
|
+
"""True if the deploy SCRIPTS (``tools/deploy_field.py`` etc.) are present -- i.e. this is a repo checkout,
|
|
85
|
+
not an installed wheel (the wheel ships no ``tools/``). The Workspace uses it to hide the dev-only deploy
|
|
86
|
+
paths (test-slot / campaign / journey / battle / reverts) for an installed copy."""
|
|
87
|
+
return (Path(repo_root) / "tools" / "deploy_field.py").is_file()
|
|
88
|
+
|
|
89
|
+
|
|
83
90
|
def detect_deployed_fields(mod_folder):
|
|
84
91
|
"""``[(id, name), ...]`` of the FieldScene lines in the worktree mod folder's DictionaryPatch -- the
|
|
85
92
|
fields whose encounter a battle-mint can repoint (the valid 'trigger field' choices)."""
|
|
@@ -1829,6 +1829,16 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
|
|
|
1829
1829
|
wb = meta["walkmesh_bounds"]
|
|
1830
1830
|
x, z = meta["player_start"]
|
|
1831
1831
|
scroll = "[camera.scroll]\nenabled = true\n" if meta["scrolling"] else ""
|
|
1832
|
+
# The donor's REAL field id (FBG -> id): a fork MIRRORS this field's geometry, so deploy auto-emits the
|
|
1833
|
+
# ForkDonorPatch `<forkId> <donorId>` -> the engine's name-keyed fidelity (s31 occlusion z-offset,
|
|
1834
|
+
# FieldLocationName) resolves for the custom id. Recorded as `[verbatim_eb] donor` (verbatim) OR
|
|
1835
|
+
# `[field] source_field` (native/synth) -- both read by build._verbatim_donor_id + emitted by deploy_field.
|
|
1836
|
+
from .dialogue import _resolve_field_id as _rfi
|
|
1837
|
+
try:
|
|
1838
|
+
_src_fid = _rfi(field)
|
|
1839
|
+
except (FileNotFoundError, ValueError):
|
|
1840
|
+
_src_fid = None
|
|
1841
|
+
source_field_line = ""
|
|
1832
1842
|
if verbatim:
|
|
1833
1843
|
# VERBATIM .eb fork (docs/FORK_FIDELITY.md, the entry-0 carry): ship the donor's WHOLE event script;
|
|
1834
1844
|
# the build runs the real logic instead of synthesizing. No declarative content (it's all in the .eb).
|
|
@@ -1845,11 +1855,7 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
|
|
|
1845
1855
|
# Donor battle BGM: a verbatim fork carries the real Battle()/BattleEx() ops, but its custom id misses
|
|
1846
1856
|
# the engine's (fldMapNo, scene) song lookup -> the boss/special theme is lost. Auto-emit [[battle_bgm]]
|
|
1847
1857
|
# for the donor's scripted battle scenes whose song is non-zero (build -> a scene-keyed Music: line).
|
|
1848
|
-
|
|
1849
|
-
try:
|
|
1850
|
-
_donor_fid = _resolve_field_id(field)
|
|
1851
|
-
except (FileNotFoundError, ValueError):
|
|
1852
|
-
_donor_fid = None
|
|
1858
|
+
_donor_fid = _src_fid
|
|
1853
1859
|
bgm_pairs = _donor_battle_bgm_pairs(donor_eb, _donor_fid, game)
|
|
1854
1860
|
bgm_blocks = _render_battle_bgm_blocks(bgm_pairs)
|
|
1855
1861
|
# retarget the Field() exits: import-chain pre-fills a LIVE table (doors warp into the chain's own
|
|
@@ -1894,6 +1900,9 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
|
|
|
1894
1900
|
field, game, out_dir=out, name=name, id_remap=id_remap, live_seams=live_seams,
|
|
1895
1901
|
graft_player_funcs=graft_player_funcs, carry_text=carry_text, graft_savepoint=graft_savepoint)
|
|
1896
1902
|
meta["imported_content"] = content_summary
|
|
1903
|
+
if _src_fid is not None and _src_fid != field_id: # record the donor -> deploy auto-emits ForkDonorPatch
|
|
1904
|
+
source_field_line = (f"source_field = {_src_fid} # the real field this NATIVE fork mirrors; deploy "
|
|
1905
|
+
f"emits ForkDonorPatch so name-keyed fidelity (occlusion/location) resolves\n")
|
|
1897
1906
|
control_line = (f"control_direction = {control_dir} # imported WASD-vs-camera tuning\n"
|
|
1898
1907
|
if control_dir is not None else "")
|
|
1899
1908
|
content_tail = (
|
|
@@ -1913,6 +1922,7 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
|
|
|
1913
1922
|
f'name = "{name}"\n'
|
|
1914
1923
|
f"area = {safe_area}\n"
|
|
1915
1924
|
f"text_block = {text_block}\n"
|
|
1925
|
+
f"{source_field_line}"
|
|
1916
1926
|
f"{_walkmesh_hotfix_line(field)}"
|
|
1917
1927
|
f"{_area_title_hide_lines(meta, verbatim=verbatim)}"
|
|
1918
1928
|
f'bgs = "scene.bgs.bytes" # NATIVE scene (per-tile depth) -> seamless render, NO .bgx / no tile seams\n'
|
|
@@ -32,6 +32,10 @@ class BuildDoc(QWidget):
|
|
|
32
32
|
self.pal = pal
|
|
33
33
|
self.repo = Path(repo_root)
|
|
34
34
|
self.kit = self.repo / "ff9mapkit" # `-m ff9mapkit build` cwd (local pkg shadows)
|
|
35
|
+
self.kit_cwd = self.kit if self.kit.is_dir() else None # None -> run_job falls back to KIT (always valid)
|
|
36
|
+
# A repo checkout has the deploy scripts at <repo>/tools/; an installed copy (pip/uv/.exe) does NOT,
|
|
37
|
+
# so the test-slot / campaign / journey DEPLOYS (+ the F6 loop) are unavailable there. `build` works either way.
|
|
38
|
+
self.has_tools = jobs.has_deploy_tools(self.repo)
|
|
35
39
|
self._run = run
|
|
36
40
|
self._problems = problems
|
|
37
41
|
self.kind = "field"
|
|
@@ -102,7 +106,7 @@ class BuildDoc(QWidget):
|
|
|
102
106
|
tid = self.worktree_id or 4003
|
|
103
107
|
self.rb_test = QRadioButton(f"Test slot {tid} — quick + reversible; play via F6 → Warp"
|
|
104
108
|
+ (" (or New Game → hut door)" if tid == 4003 else ""))
|
|
105
|
-
self.rb_test.setChecked(
|
|
109
|
+
self.rb_test.setChecked(self.has_tools) # installed copy: no F6 dev engine -> default to Install to game
|
|
106
110
|
self.rb_game = QRadioButton(f"Install to game (shipping mod folder): {self.game_mod}"
|
|
107
111
|
if self.game_mod else "Install to game — (game install not found)")
|
|
108
112
|
if not self.game_mod:
|
|
@@ -126,6 +130,10 @@ class BuildDoc(QWidget):
|
|
|
126
130
|
self.dest.setWordWrap(True)
|
|
127
131
|
self.dest.setStyleSheet(f"color:{self.pal['accent']};")
|
|
128
132
|
gv.addWidget(self.dest)
|
|
133
|
+
if not self.has_tools: # installed: no test-slot/F6 -> default to Install to game / Build only
|
|
134
|
+
self.rb_test.setEnabled(False)
|
|
135
|
+
self.rb_test.setText(self.rb_test.text() + " (dev repo only)")
|
|
136
|
+
(self.rb_game if self.game_mod else self.rb_other).setChecked(True) # safe now: self.dest exists
|
|
129
137
|
self.field_box = box
|
|
130
138
|
return box
|
|
131
139
|
|
|
@@ -375,6 +383,21 @@ class BuildDoc(QWidget):
|
|
|
375
383
|
def _info(self, title, text):
|
|
376
384
|
QMessageBox.information(self, title, text)
|
|
377
385
|
|
|
386
|
+
def _require_tools(self, what):
|
|
387
|
+
"""Installed (non-repo) copies don't ship the deploy SCRIPTS (tools/). Show a clear, actionable
|
|
388
|
+
message instead of a cryptic 'no such file' and return False; True when the repo tools are present."""
|
|
389
|
+
if self.has_tools:
|
|
390
|
+
return True
|
|
391
|
+
self._warn(
|
|
392
|
+
f"{what} needs the source repo",
|
|
393
|
+
f"'{what}' runs ff9mapkit's development deploy scripts (the repo's tools/), which aren't part of "
|
|
394
|
+
"an installed copy.\n\n"
|
|
395
|
+
"To get a custom field into your game from an installed ff9mapkit:\n"
|
|
396
|
+
" - use Build to -> 'Install to game' (it writes the mod into your FF9 folder; Memoria detects\n"
|
|
397
|
+
" it automatically), then reach it via a [[gateway]] from an early field or by wiring New Game.\n\n"
|
|
398
|
+
"(The test-slot + F6 loop and reversible campaign/journey deploys are a dev-repo workflow.)")
|
|
399
|
+
return False
|
|
400
|
+
|
|
378
401
|
def _picked(self):
|
|
379
402
|
f = self.path.text().strip().strip('"')
|
|
380
403
|
if not f or not Path(f).is_file():
|
|
@@ -463,6 +486,8 @@ class BuildDoc(QWidget):
|
|
|
463
486
|
|
|
464
487
|
def _go_field(self, field):
|
|
465
488
|
if self.rb_test.isChecked():
|
|
489
|
+
if not self._require_tools("Deploy to test slot"):
|
|
490
|
+
return
|
|
466
491
|
tid = self.worktree_id or 4003
|
|
467
492
|
reach = ("New Game → walk to the hut door (or F6 → Warp)" if tid == 4003
|
|
468
493
|
else f"F6 → Warp to field {tid}")
|
|
@@ -477,20 +502,22 @@ class BuildDoc(QWidget):
|
|
|
477
502
|
if self._confirm("Install to game",
|
|
478
503
|
f"Build this field into the game mod folder?\n\n{self.game_mod}\n\n"
|
|
479
504
|
"Writes the field at its real id (may overwrite a field with the same id)."):
|
|
480
|
-
self._stream(jobs.build_argv(field, str(self.game_mod)), cwd=self.
|
|
505
|
+
self._stream(jobs.build_argv(field, str(self.game_mod)), cwd=self.kit_cwd,
|
|
481
506
|
subject="Install to game", ok_headline=f"Built into {self.game_mod}")
|
|
482
507
|
else:
|
|
483
508
|
out = self.other.text().strip()
|
|
484
509
|
if not out:
|
|
485
510
|
return self._warn("No folder", "Pick an output folder.")
|
|
486
|
-
self._stream(jobs.build_argv(field, out), cwd=self.
|
|
511
|
+
self._stream(jobs.build_argv(field, out), cwd=self.kit_cwd, subject="Build",
|
|
487
512
|
ok_headline=f"Built into {out}")
|
|
488
513
|
|
|
489
514
|
def _go_campaign(self, path):
|
|
490
515
|
if self.rb_camp_build.isChecked():
|
|
491
|
-
self._stream(jobs.build_campaign_argv(path), cwd=self.
|
|
516
|
+
self._stream(jobs.build_campaign_argv(path), cwd=self.kit_cwd, subject="Build campaign",
|
|
492
517
|
ok_headline=f"Built campaign {self.plan.name}")
|
|
493
518
|
return
|
|
519
|
+
if not self._require_tools("Deploy campaign"):
|
|
520
|
+
return
|
|
494
521
|
wire = self.wire_newgame.isChecked()
|
|
495
522
|
route = ("It also wires New Game to enter the chain (experimental)." if wire
|
|
496
523
|
else "Reach each screen in-game via F6 → Warp.")
|
|
@@ -505,6 +532,8 @@ class BuildDoc(QWidget):
|
|
|
505
532
|
ok_next=f"Relaunch once (new DictionaryPatch), then F6 → Warp → {entry} to walk the chain.")
|
|
506
533
|
|
|
507
534
|
def _go_journey(self, path):
|
|
535
|
+
if not self._require_tools("Deploy journey"):
|
|
536
|
+
return
|
|
508
537
|
if self.rb_jour_preview.isChecked(): # dry-run: print the playbook, no game writes -> no confirm
|
|
509
538
|
self._stream(jobs.deploy_journey_argv(self.repo, path), cwd=self.repo,
|
|
510
539
|
subject="Journey deploy playbook (dry-run)",
|
|
@@ -550,6 +579,8 @@ class BuildDoc(QWidget):
|
|
|
550
579
|
ok_next=stackmsg)
|
|
551
580
|
|
|
552
581
|
def _go_battle(self, battle):
|
|
582
|
+
if not self._require_tools("Deploy battle map"):
|
|
583
|
+
return
|
|
553
584
|
trig = self.trigger.text().strip()
|
|
554
585
|
if trig and not trig.isdigit():
|
|
555
586
|
return self._warn("Bad trigger field", "Trigger field must be a field id number (or blank).")
|
|
@@ -566,6 +597,8 @@ class BuildDoc(QWidget):
|
|
|
566
597
|
|
|
567
598
|
# ------------------------------------------------------------------ New Game entry (hub-less)
|
|
568
599
|
def on_set_newgame(self):
|
|
600
|
+
if not self._require_tools("Set New Game entry"):
|
|
601
|
+
return
|
|
569
602
|
fid = self.newgame_id.text().strip()
|
|
570
603
|
if not fid.isdigit():
|
|
571
604
|
return self._warn("Bad field id", "Enter the numeric field id New Game should land on "
|
|
@@ -580,6 +613,8 @@ class BuildDoc(QWidget):
|
|
|
580
613
|
ok_next="Relaunch the game, then New Game. Undo with 'Revert New Game'.")
|
|
581
614
|
|
|
582
615
|
def on_revert_newgame(self):
|
|
616
|
+
if not self._require_tools("Revert New Game"):
|
|
617
|
+
return
|
|
583
618
|
argv = jobs.revert_newgame_argv(self.repo) # most-recent New-Game revert (from-stock OR retarget)
|
|
584
619
|
if argv is None or not Path(argv[-1]).exists():
|
|
585
620
|
return self._info("Nothing to revert", "No New-Game change to undo yet.")
|
|
@@ -590,6 +625,8 @@ class BuildDoc(QWidget):
|
|
|
590
625
|
|
|
591
626
|
# ------------------------------------------------------------------ Revert
|
|
592
627
|
def on_revert(self):
|
|
628
|
+
if not self._require_tools("Revert"):
|
|
629
|
+
return
|
|
593
630
|
if self.kind == "battle":
|
|
594
631
|
argv, what = jobs.revert_battle_argv(self.repo), "battle"
|
|
595
632
|
elif self.kind == "campaign":
|
|
@@ -30,6 +30,10 @@ class ImportDoc(QWidget):
|
|
|
30
30
|
super().__init__()
|
|
31
31
|
self.pal = pal
|
|
32
32
|
self.kit = Path(kit_root) # `-m ff9mapkit` cwd (this worktree's package)
|
|
33
|
+
# Default output base: the repo parent for a checkout; an installed copy's package dir is inside a
|
|
34
|
+
# venv, so write forked projects to a discoverable user folder instead (not buried in site-packages).
|
|
35
|
+
self.proj_base = (self.kit.parent if (self.kit / "pyproject.toml").is_file()
|
|
36
|
+
else Path.home() / "Dream World IX")
|
|
33
37
|
self._run = run
|
|
34
38
|
self._on_forked = on_forked # called with the output DIR on a clean fork -> shell opens it
|
|
35
39
|
# The tab body SCROLLS: this view stacks five tall group boxes, so a short window would otherwise
|
|
@@ -152,7 +156,7 @@ class ImportDoc(QWidget):
|
|
|
152
156
|
|
|
153
157
|
out = QHBoxLayout()
|
|
154
158
|
out.addWidget(QLabel("Write to:"))
|
|
155
|
-
self.out = QLineEdit(str(self.
|
|
159
|
+
self.out = QLineEdit(str(self.proj_base / "imported"))
|
|
156
160
|
browse = QPushButton("Browse…")
|
|
157
161
|
browse.clicked.connect(self.browse_out)
|
|
158
162
|
out.addWidget(self.out, 1)
|
|
@@ -269,7 +273,7 @@ class ImportDoc(QWidget):
|
|
|
269
273
|
v.addWidget(swap_hint)
|
|
270
274
|
out = QHBoxLayout()
|
|
271
275
|
out.addWidget(QLabel("Write campaign to:"))
|
|
272
|
-
self.rg_out = QLineEdit(str(self.
|
|
276
|
+
self.rg_out = QLineEdit(str(self.proj_base / "campaign"))
|
|
273
277
|
rbrowse = QPushButton("Browse…")
|
|
274
278
|
rbrowse.clicked.connect(self.browse_region_out)
|
|
275
279
|
out.addWidget(self.rg_out, 1)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ff9mapkit"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0b6"
|
|
8
8
|
description = "Author novel custom field maps for Final Fantasy IX (Memoria engine) from a declarative TOML project file."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11" # tomllib is stdlib from 3.11
|
|
@@ -369,3 +369,20 @@ def _w(tmp_path, toml):
|
|
|
369
369
|
p = tmp_path / "ok.field.toml"
|
|
370
370
|
p.write_text(toml, encoding="utf-8")
|
|
371
371
|
return p
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_legacy_cutscene_ate_is_deprecated(tmp_path):
|
|
375
|
+
"""`[cutscene] ate = true` (the OLD held-banner model) now lint-warns toward the faithful `[[gateway]] ate`
|
|
376
|
+
warp-in trigger -- it still builds (a warning, not an error)."""
|
|
377
|
+
from ff9mapkit.build import FieldProject, lint_logic, validate
|
|
378
|
+
base = ('[field]\nid = 4003\nname = "D"\narea = 11\ntext_block = 1073\n\n'
|
|
379
|
+
'[camera]\npitch = 45\nfov = 42.2\n\n'
|
|
380
|
+
'[walkmesh]\nquad = [[-200,-200],[200,-200],[200,200],[-200,200]]\n\n'
|
|
381
|
+
'[player]\nspawn = [0, 0]\n\n')
|
|
382
|
+
p = tmp_path / "d.field.toml"
|
|
383
|
+
p.write_text(base + '[cutscene]\nate = true\nsteps = [ { say = "An ATE." } ]\n', encoding="utf-8")
|
|
384
|
+
proj = FieldProject.load(p)
|
|
385
|
+
assert any("held-banner" in w and "[[gateway]] ate" in w for w in lint_logic(proj))
|
|
386
|
+
assert validate(proj) == [] # deprecated, but still valid (warning, not error)
|
|
387
|
+
p.write_text(base + '[cutscene]\nsteps = [ { say = "Hi." } ]\n', encoding="utf-8") # no ate -> no warning
|
|
388
|
+
assert not any("held-banner" in w for w in lint_logic(FieldProject.load(p)))
|
|
@@ -132,3 +132,53 @@ def test_install_engine_refuses_without_memoria(tmp_path):
|
|
|
132
132
|
def test_bundle_missing_dll_raises(tmp_path):
|
|
133
133
|
with pytest.raises(ValueError):
|
|
134
134
|
memoria.bundle_dll_members(_make_bundle(tmp_path, complete=False))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---- game-install detection (Steam + GOG; rejects MS Store) ----------------------------------------
|
|
138
|
+
def _make_ff9_root(tmp_path, *, launcher=True, streaming=True, managed=True):
|
|
139
|
+
root = tmp_path / "FINAL FANTASY IX"
|
|
140
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
if launcher:
|
|
142
|
+
(root / "FF9_Launcher.exe").write_bytes(b"")
|
|
143
|
+
if streaming:
|
|
144
|
+
(root / "StreamingAssets").mkdir(exist_ok=True)
|
|
145
|
+
if managed:
|
|
146
|
+
(root / "x64" / "FF9_Data" / "Managed").mkdir(parents=True, exist_ok=True)
|
|
147
|
+
return root
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_is_ff9_root_accepts_real_layout(tmp_path):
|
|
151
|
+
assert config._is_ff9_root(_make_ff9_root(tmp_path)) is True
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_is_ff9_root_rejects_incomplete(tmp_path):
|
|
155
|
+
assert config._is_ff9_root(_make_ff9_root(tmp_path / "a", launcher=False)) is False # MS Store-like
|
|
156
|
+
assert config._is_ff9_root(_make_ff9_root(tmp_path / "b", streaming=False)) is False
|
|
157
|
+
assert config._is_ff9_root(_make_ff9_root(tmp_path / "c", managed=False)) is False
|
|
158
|
+
assert config._is_ff9_root(tmp_path / "nope") is False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_parse_vdf_new_schema():
|
|
162
|
+
text = (
|
|
163
|
+
'"libraryfolders"\n{\n'
|
|
164
|
+
' "0"\n {\n'
|
|
165
|
+
' "path" "C:\\\\Program Files (x86)\\\\Steam"\n'
|
|
166
|
+
' "apps" { "377840" "12345678" }\n' # appid->buildid must NOT be read as a library
|
|
167
|
+
' }\n'
|
|
168
|
+
' "1"\n {\n "path" "E:\\\\SteamLibrary"\n }\n}\n'
|
|
169
|
+
)
|
|
170
|
+
assert config._parse_vdf_library_paths(text) == [r"C:\Program Files (x86)\Steam", r"E:\SteamLibrary"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_parse_vdf_old_schema():
|
|
174
|
+
text = '"LibraryFolders"\n{\n "1" "E:\\\\Games\\\\Steam"\n "2" "F:\\\\Steam"\n}\n'
|
|
175
|
+
assert config._parse_vdf_library_paths(text) == [r"E:\Games\Steam", r"F:\Steam"]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---- installed vs repo: the GUI deploy-tools gate (PySide6-free; the Qt shell is covered by --smoke) ----
|
|
179
|
+
def test_has_deploy_tools(tmp_path):
|
|
180
|
+
from ff9mapkit.editor import jobs
|
|
181
|
+
assert jobs.has_deploy_tools(tmp_path) is False # installed-like: no tools/ in the wheel
|
|
182
|
+
(tmp_path / "tools").mkdir()
|
|
183
|
+
(tmp_path / "tools" / "deploy_field.py").write_text("", encoding="utf-8")
|
|
184
|
+
assert jobs.has_deploy_tools(tmp_path) is True # repo checkout
|
|
@@ -90,6 +90,21 @@ def test_remap_fields_patches_destinations():
|
|
|
90
90
|
assert _vb.remap_fields(eb, {}) == eb
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def test_fork_donor_id_reads_native_source_field():
|
|
94
|
+
"""deploy_field auto-emits ForkDonorPatch for NATIVE/SYNTH forks too: build._verbatim_donor_id resolves the
|
|
95
|
+
donor from `[field] source_field` (the native import's record), not only `[verbatim_eb] donor`."""
|
|
96
|
+
from ff9mapkit.build import _verbatim_donor_id
|
|
97
|
+
|
|
98
|
+
class _P:
|
|
99
|
+
def __init__(self, raw):
|
|
100
|
+
self.raw = raw
|
|
101
|
+
|
|
102
|
+
assert _verbatim_donor_id(_P({"field": {"source_field": 351}})) == 351 # native fork
|
|
103
|
+
assert _verbatim_donor_id(_P({"verbatim_eb": {"donor": 1860}})) == 1860 # verbatim fork (unchanged)
|
|
104
|
+
assert _verbatim_donor_id(_P({"field": {"borrow_field": 100}})) == 100 # BG-borrow form
|
|
105
|
+
assert _verbatim_donor_id(_P({"field": {}})) is None # a non-fork synth field -> no emit
|
|
106
|
+
|
|
107
|
+
|
|
93
108
|
def _game_ready():
|
|
94
109
|
try:
|
|
95
110
|
import UnityPy # noqa: F401,PLC0415
|
|
@@ -136,6 +151,22 @@ def test_import_verbatim_ships_the_whole_donor_eb(tmp_path):
|
|
|
136
151
|
assert 4100 in _fields(shipped) and exits[0] not in _fields(shipped)
|
|
137
152
|
|
|
138
153
|
|
|
154
|
+
@pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
|
|
155
|
+
def test_native_import_records_donor_for_forkdonorpatch(tmp_path):
|
|
156
|
+
# A NATIVE (non-verbatim) import now records `[field] source_field = <donor real id>`, so deploy_field can
|
|
157
|
+
# auto-emit ForkDonorPatch (name-keyed occlusion/location fidelity) -- no more hand-written `<fork> <donor>`.
|
|
158
|
+
from ff9mapkit import extract
|
|
159
|
+
from ff9mapkit.build import FieldProject, _verbatim_donor_id
|
|
160
|
+
from ff9mapkit.dialogue import _resolve_field_id
|
|
161
|
+
fbg = "fbg_n06_vgdl_map101_dl_inn_0"
|
|
162
|
+
donor = _resolve_field_id(fbg)
|
|
163
|
+
_m, toml = extract.write_native_project(fbg, tmp_path, name="DV") # native scene, NOT verbatim
|
|
164
|
+
proj = FieldProject.load(toml)
|
|
165
|
+
assert "verbatim_eb" not in proj.raw # it's a native fork
|
|
166
|
+
assert proj.raw["field"].get("source_field") == donor # donor recorded in [field]
|
|
167
|
+
assert _verbatim_donor_id(proj) == donor # -> deploy_field emits ForkDonorPatch
|
|
168
|
+
|
|
169
|
+
|
|
139
170
|
@pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
|
|
140
171
|
def test_build_field_verbatim_with_logic_edit_end_to_end(tmp_path):
|
|
141
172
|
# REGRESSION: a FULL build of a verbatim fork must run build_field's per-language loop (which reads
|
|
@@ -374,6 +405,34 @@ def test_build_field_verbatim_player_walk_end_to_end(tmp_path):
|
|
|
374
405
|
assert (2, 250, 20) in calls # conductor RunScriptSync(2, player=250, 20)
|
|
375
406
|
|
|
376
407
|
|
|
408
|
+
@pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
|
|
409
|
+
def test_build_field_verbatim_conductor_exit_warp_end_to_end(tmp_path):
|
|
410
|
+
# A conductor on a verbatim fork with `exit_warp`: the below-band director ENDS with a fade + Field(target)
|
|
411
|
+
# (the warp-back) instead of EnableMove -- the player is warped out after the scene (the same lever the
|
|
412
|
+
# forced-ATE scene uses to return the player). exit_warp sits OUTSIDE the once-gate so it always fires.
|
|
413
|
+
from ff9mapkit import build, extract
|
|
414
|
+
from ff9mapkit.eb import EbScript
|
|
415
|
+
from ff9mapkit.content import object as _object
|
|
416
|
+
_meta, toml = extract.write_native_project("fbg_n06_vgdl_map101_dl_inn_0", tmp_path, name="DV", verbatim=True)
|
|
417
|
+
donor = EbScript.from_bytes(extract.extract_event_script("fbg_n06_vgdl_map101_dl_inn_0"))
|
|
418
|
+
project = build.FieldProject.load(toml)
|
|
419
|
+
project.raw["npc"] = [{"name": "lefty", "preset": "vivi", "pos": [100, 200], "dialogue": "."}]
|
|
420
|
+
project.raw["cutscene"] = {"once": True, "actor": ["lefty"], "exit_warp": 1153,
|
|
421
|
+
"steps": [{"actor": "lefty", "say": "The scene ends -- and out you go."}]}
|
|
422
|
+
assert build.validate(project) == []
|
|
423
|
+
out = tmp_path / "mod"
|
|
424
|
+
build.build_mod([project], out, mod_name="FF9CustomMap") # must not raise
|
|
425
|
+
band_lo = donor.entry_count - _object.PARTY_BAND_SIZE # lefty=band_lo, conductor=band_lo+1
|
|
426
|
+
ebs = [p for p in out.rglob("*.eb.bytes")]
|
|
427
|
+
assert ebs
|
|
428
|
+
for p in ebs:
|
|
429
|
+
s = EbScript.from_bytes(p.read_bytes())
|
|
430
|
+
cond = s.entry(band_lo + 1).func_by_tag(0)
|
|
431
|
+
ops = [i.op for i in s.instrs(cond)]
|
|
432
|
+
assert 1153 in [i.imm(0) for i in s.instrs(cond) if i.op == 0x2B] # ends with Field(exit_warp) -- the warp-back
|
|
433
|
+
assert 0x2E not in ops # NO EnableMove (the destination restores control)
|
|
434
|
+
|
|
435
|
+
|
|
377
436
|
@pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
|
|
378
437
|
def test_build_field_verbatim_with_readable_prop_end_to_end(tmp_path):
|
|
379
438
|
# A readable [[prop]] (dialogue=) ADDED to a verbatim fork: the below-band prop object is NON-bare -- it
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|