ff9mapkit 1.0.0b3__tar.gz → 1.0.0b4__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.0b3 → ff9mapkit-1.0.0b4}/PKG-INFO +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/__init__.py +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/build.py +144 -48
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/cli.py +86 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/config.py +20 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/conductor.py +137 -51
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/cutscene.py +70 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/text.py +7 -3
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/dialogue.py +1 -1
- ff9mapkit-1.0.0b4/ff9mapkit/memoria.py +91 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/provision.py +29 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/PKG-INFO +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/SOURCES.txt +2 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/pyproject.toml +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_ate.py +107 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_campaign.py +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_content.py +122 -13
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_flags.py +1 -1
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_on_entry.py +2 -2
- ff9mapkit-1.0.0b4/tests/test_setup.py +134 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_text.py +4 -4
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_verbatim.py +63 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/LICENSE +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/README.md +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/__main__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_animdb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_animdb_all.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_fieldtable.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_fieldtext.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_held_poses.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_itemdb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_modeldb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_narrowmap_data.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_npcparams.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_animdb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_animdb_all.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_fieldtable.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_fieldtext.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_modeldb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_npcparams.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_regen_scenedb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/_scenedb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/abilities.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/animations.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/archetypes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/areatitle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/abilityfeatures.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/actiondelta.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/aiauthor.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/ailint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/aipatch.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/battleai.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/battlecsv.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/battlepatch.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/build.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/camera_codec.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/camera_data.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/characterdelta.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/event_data.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/extract.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/fbx.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/reskin.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/scene_codec.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/scene_data.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/scenelint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/seqasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/seqauthor.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/seqcodec.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/seqdis.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle/seqpatch.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/battle_bgm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/binutils.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/campaign.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/catalog.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/chain.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/areatitle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/ate.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/camera.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/chest.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/choice.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/encounter.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/entry_settle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/equipment.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/event.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/gateway.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/inventory.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/itemdata.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/itemtext.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/jump.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/ladder.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/movement.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/music.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/npc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/object.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/onentry.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/party.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/pathfind.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/platform.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/player.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/prop.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/region.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/reinit.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/savepoint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/shop.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/sps_trigger.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/startup.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/synthesis.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/textcarry.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/verbatim.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/content/walkmesh_hotfix.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/_regen_provenance.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.es.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.fr.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.gr.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.it.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.jp.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.uk.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/blank.us.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/manifest.json +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/provenance/region_template.patch +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/reference_arcs.toml +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/data/region_catalog.toml +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/deploystack.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/_exprtable.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/_membertable.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/_optables.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/_regen_optables.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/cmdasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/disasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/edit.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/exprasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/model.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eb/opcodes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eblint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/app.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/battle_forms.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/breadcrumb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/dialogs.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/feedback.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/forms.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/graphview.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/jobs.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/model.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/picker.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/editor/theme.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/eventscan.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/extract.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/flags.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/forkreport.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/hub.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/idgated.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/infohub.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/items.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/itemstats.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/journey.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/keyitems.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/logic_add.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/logic_edit.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/logic_map.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/pack.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/playerswap.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/prop_archetypes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/refarc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/save.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/save_items.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/arena.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/bgart.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/bgi.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/bgs.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/bgx.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/cam.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/guide.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/paint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/scene/placeholder.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sjbinary.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/author.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/catalog.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/codec.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/edit.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/lint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/render.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/templates.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/sps/texture.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/walkmesh_hotfixes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/__init__.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/battledoc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/builddoc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/forms_qt.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/importdoc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/mapview.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/palette.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/savedoc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/shell.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/style.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit/workspace/tuningdialog.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/dependency_links.txt +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/entry_points.txt +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/requires.txt +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/ff9mapkit.egg-info/top_level.txt +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/setup.cfg +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_abilities.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_abilityfeatures.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_actiondelta.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_ai_phase_insert.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_ai_phase_insert_adversary.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_aiauthor.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_ailint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_aipatch.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_animations.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_archetypes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_areatitle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_arming.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battle_bgm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battle_forms.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battle_scene_codec.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battle_seq.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battleai.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battlecsv.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_battlepatch.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_bgart.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_bgs.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_build.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_cameras.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_capstone.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_carry_text_lint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_catalog.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_chain.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_characterdelta.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_choice.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_cli_entry.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_cmdasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_cmdasm_relocate.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_deploy_campaign.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_deploystack.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_dialogue.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_eb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_eblint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_app.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_breadcrumb.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_feedback.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_forms.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_integration.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_jobs.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_model.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_editor_theme.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_entry_settle.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_eventscan.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_export.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_exprasm.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_extract_area.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_find_field.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_forkreport.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_gateway_advance.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_graphview.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_hub_gen.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_idgated.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_import_borrow.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_infohub.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_itemdata.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_items.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_itemstats.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_itemtext.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_journey.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_journey_merge.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_jump.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_ladder.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_lint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_logic_add.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_logic_edit.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_logic_map.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_movement.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_npc_model.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_npc_verbatim.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_npcparams.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_object_graft.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_occlusion.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_pack.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_paint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_party.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_platform.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_player_graft.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_playerswap.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_prop_archetypes.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_provision.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_refarc.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_repaint_native.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_reskin.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_save.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_save_items.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_savepoint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_scene.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_scenelint.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_scroll.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_shared_text_block.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_shop.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_showcase.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_sjbinary.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_spawn.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_sps.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_startstate.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_startup.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_synthesis.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_textcarry.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_walkmesh_hotfix.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_workspace_style.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/tests/test_world_hub.py +0 -0
- {ff9mapkit-1.0.0b3 → ff9mapkit-1.0.0b4}/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.0b4" # keep in lockstep with [project] version in pyproject.toml
|
|
@@ -863,6 +863,22 @@ def validate(project: FieldProject) -> list[str]:
|
|
|
863
863
|
# on-exit story advance: set_scenario / set_flags fire when the player takes this exit
|
|
864
864
|
_validate_story_writes(gw, "[[gateway]]", story_names, problems,
|
|
865
865
|
scenario_key="set_scenario", flags_key="set_flags")
|
|
866
|
+
# forced-ATE warp-in: ate = true flashes the grey banner WARNING before this exit warps (the faithful
|
|
867
|
+
# grey-ATE trigger -- pair with a plain [cutscene] + exit_warp on the destination, which returns you).
|
|
868
|
+
if "ate_mode" in gw and not gw.get("ate"):
|
|
869
|
+
problems.append("[[gateway]] ate_mode is set but ate is not true -- set ate = true to make this "
|
|
870
|
+
"exit a forced-ATE warp (grey banner warning, then warp), or drop ate_mode.")
|
|
871
|
+
if gw.get("ate"):
|
|
872
|
+
m = gw.get("ate_mode", _cutscene.ATE_DEFAULT_MODE)
|
|
873
|
+
if not isinstance(m, int) or isinstance(m, bool) or not (0 <= m <= 255):
|
|
874
|
+
problems.append(f"[[gateway]] ate_mode {m!r} must be an int 0..255 "
|
|
875
|
+
f"(6 = grey unskippable ATE banner; ATE 0xD7 mode).")
|
|
876
|
+
if "ate_title" in gw:
|
|
877
|
+
if not gw.get("ate"):
|
|
878
|
+
problems.append("[[gateway]] ate_title is set but ate is not true -- set ate = true (it's the "
|
|
879
|
+
"title window the forced-ATE warp-in shows), or drop ate_title.")
|
|
880
|
+
elif not isinstance(gw["ate_title"], str) or not gw["ate_title"].strip():
|
|
881
|
+
problems.append("[[gateway]] ate_title must be a non-empty string (the ATE title-window text).")
|
|
866
882
|
for ev in project.raw.get("event", []):
|
|
867
883
|
z = ev.get("zone", [])
|
|
868
884
|
if len(z) not in (4, 5):
|
|
@@ -2475,7 +2491,9 @@ def _gateway_on_exit_body(gw: dict, names: dict) -> bytes:
|
|
|
2475
2491
|
"""The story-state advance a ``[[gateway]]`` applies when the player TAKES this exit: the raw
|
|
2476
2492
|
``set_var`` bytes from ``set_scenario`` (ScenarioCounter) + ``set_flags`` (gEventGlobal bits), built
|
|
2477
2493
|
with the shared :func:`ff9mapkit.content.startup.startup_body`. ``b""`` when the gateway has neither
|
|
2478
|
-
(so the build is byte-identical to a gateway without on-exit writes).
|
|
2494
|
+
(so the build is byte-identical to a gateway without on-exit writes). (NB ``ate = true`` is NOT handled
|
|
2495
|
+
here -- a forced-ATE warp-in routes to :func:`cutscene.inject_forced_ate` instead, because the banner +
|
|
2496
|
+
timed warp must run in a RunScript'd player func, not inline in the region, or the region's Wait freezes.)"""
|
|
2479
2497
|
sc = gw.get("set_scenario")
|
|
2480
2498
|
if isinstance(sc, str):
|
|
2481
2499
|
sc = _flags.resolve_scenario(sc)
|
|
@@ -3251,6 +3269,61 @@ def _inject_verbatim_npcs(project: FieldProject, eb: bytes, npc_txids: dict, *,
|
|
|
3251
3269
|
return eb, npc_slots
|
|
3252
3270
|
|
|
3253
3271
|
|
|
3272
|
+
def _gen_conductor_walk_tags(project: FieldProject, eb: bytes, steps, npc_slots):
|
|
3273
|
+
"""Shared by the synth + verbatim conductor wiring: add the per-actor choreography tags a conductor's
|
|
3274
|
+
``walk`` beats need. For each ``walk`` step, add a walk tag (20+) to the actor's entry (the conductor
|
|
3275
|
+
``RunScript``s into it -- base ``Walk`` acts on the executing object, so the walk must run in the actor's
|
|
3276
|
+
own context). For each actor that walks inside a PARALLEL group (``with_prev``), also add ONE bare-RETURN
|
|
3277
|
+
join tag -- the conductor async-forks the walk then ``RunScriptSync``s the join tag to block until the
|
|
3278
|
+
walk frees the actor's script level. ``add_function`` grows an entry but keeps slot INDICES stable, so the
|
|
3279
|
+
conductor's by-uid refs (and any later band insert) stay valid. Returns ``(eb, walk_calls, join_tags)``.
|
|
3280
|
+
|
|
3281
|
+
The PLAYER walks too: ``"player"`` -> the tag goes on the player's OWN entry (``DefinePlayerCharacter``,
|
|
3282
|
+
located by :func:`ff9mapkit.content.player.find_player_entry`), but the conductor addresses it by the
|
|
3283
|
+
control-char sentinel uid 250 (``GetObjUID(250)`` -> the control character) -- so its add-target entry and
|
|
3284
|
+
its conductor uid differ (an NPC's are the same, slot == uid). Same recipe the ladder uses for the player."""
|
|
3285
|
+
walk_calls, join_tags = {}, {}
|
|
3286
|
+
if not any("walk" in s for s in steps):
|
|
3287
|
+
return eb, walk_calls, join_tags
|
|
3288
|
+
from .eb import edit as _eb_edit
|
|
3289
|
+
from .content import player as _player
|
|
3290
|
+
move_reg = _position_registry(project)
|
|
3291
|
+
# the player entry index is stable across add_function (slot indices don't move), so resolve it once
|
|
3292
|
+
player_entry = (_player.find_player_entry(EbScript.from_bytes(eb))
|
|
3293
|
+
if any(s.get("actor") == "player" and "walk" in s for s in steps) else None)
|
|
3294
|
+
|
|
3295
|
+
def _walk_target(actor):
|
|
3296
|
+
"""(entry index to add the tag to, uid the conductor addresses). Player: its own entry, uid 250."""
|
|
3297
|
+
if actor == "player":
|
|
3298
|
+
return player_entry, _conductor.PLAYER_UID
|
|
3299
|
+
slot = npc_slots.get(actor) # an NPC: entry index == runtime uid
|
|
3300
|
+
return slot, slot
|
|
3301
|
+
|
|
3302
|
+
next_tag = {} # actor name -> next free walk tag
|
|
3303
|
+
for i, s in enumerate(steps):
|
|
3304
|
+
if "walk" not in s:
|
|
3305
|
+
continue
|
|
3306
|
+
actor = s.get("actor")
|
|
3307
|
+
entry_idx, uid = _walk_target(actor)
|
|
3308
|
+
pt = _resolve_point(s["walk"], move_reg)
|
|
3309
|
+
t = next_tag.get(actor, _conductor.WALK_TAG_BASE)
|
|
3310
|
+
next_tag[actor] = t + 1
|
|
3311
|
+
eb = _eb_edit.add_function(eb, entry_idx, t, _conductor.walk_tag_body(pt[0], pt[1], s.get("speed")))
|
|
3312
|
+
walk_calls[i] = (uid, t)
|
|
3313
|
+
# actors that walk inside a parallel group (>1 member) need a join tag (async-fork + sync-drain)
|
|
3314
|
+
parallel_actors = set()
|
|
3315
|
+
for g in _conductor.group_parallel(steps):
|
|
3316
|
+
if len(g) > 1:
|
|
3317
|
+
parallel_actors |= {s.get("actor") for _i, s in g if "walk" in s and s.get("actor")}
|
|
3318
|
+
for actor in sorted(parallel_actors):
|
|
3319
|
+
entry_idx, uid = _walk_target(actor)
|
|
3320
|
+
if entry_idx is None:
|
|
3321
|
+
continue
|
|
3322
|
+
eb = _eb_edit.add_function(eb, entry_idx, _conductor.PARALLEL_JOIN_TAG, _conductor.join_tag_body())
|
|
3323
|
+
join_tags[uid] = _conductor.PARALLEL_JOIN_TAG
|
|
3324
|
+
return eb, walk_calls, join_tags
|
|
3325
|
+
|
|
3326
|
+
|
|
3254
3327
|
def _inject_verbatim_conductor(project: FieldProject, eb: bytes, npc_slots: dict, cutscene_txids, *, warnings) -> bytes:
|
|
3255
3328
|
"""Inject a MULTI-ACTOR ``[cutscene]`` conductor into a VERBATIM fork: ONE director code entry, seated
|
|
3256
3329
|
BELOW the donor's party band (so the 9 characters stay the top slots), that drives the additive ``[[npc]]``
|
|
@@ -3263,22 +3336,8 @@ def _inject_verbatim_conductor(project: FieldProject, eb: bytes, npc_slots: dict
|
|
|
3263
3336
|
return eb
|
|
3264
3337
|
steps = _resolve_conductor_steps(cs["steps"], project)
|
|
3265
3338
|
# walk beats -> a walk-choreography tag on the actor's below-band entry, RunScript'd by the conductor (the
|
|
3266
|
-
# synth path's mechanism; here the actor entries sit below the band)
|
|
3267
|
-
|
|
3268
|
-
walk_calls = {}
|
|
3269
|
-
if any("walk" in s for s in steps):
|
|
3270
|
-
from .eb import edit as _eb_edit
|
|
3271
|
-
move_reg = _position_registry(project)
|
|
3272
|
-
next_tag = {}
|
|
3273
|
-
for i, s in enumerate(steps):
|
|
3274
|
-
if "walk" not in s:
|
|
3275
|
-
continue
|
|
3276
|
-
slot = npc_slots.get(s.get("actor")) # validated to be a real NPC actor (not player)
|
|
3277
|
-
pt = _resolve_point(s["walk"], move_reg)
|
|
3278
|
-
t = next_tag.get(s["actor"], _conductor.WALK_TAG_BASE)
|
|
3279
|
-
next_tag[s["actor"]] = t + 1
|
|
3280
|
-
eb = _eb_edit.add_function(eb, slot, t, _conductor.walk_tag_body(pt[0], pt[1], s.get("speed")))
|
|
3281
|
-
walk_calls[i] = (slot, t)
|
|
3339
|
+
# synth path's mechanism; here the actor entries sit below the band); a parallel walk also gets a join tag.
|
|
3340
|
+
eb, walk_calls, join_tags = _gen_conductor_walk_tags(project, eb, steps, npc_slots)
|
|
3282
3341
|
auto = _FlagAlloc(getattr(project, "flag_base", None)) # campaign-safe once-flag (matches the other verbatim blocks)
|
|
3283
3342
|
c_fclass, c_fidx = _cutscene.once_flag_for(cs)
|
|
3284
3343
|
if auto.base is not None and "flag" not in cs and cs.get("once", True):
|
|
@@ -3289,7 +3348,7 @@ def _inject_verbatim_conductor(project: FieldProject, eb: bytes, npc_slots: dict
|
|
|
3289
3348
|
warmup=int(cs.get("warmup", _cutscene.DEFAULT_WARMUP)),
|
|
3290
3349
|
owns_control=bool(cs.get("owns_control", True)),
|
|
3291
3350
|
exit_warp=(int(cs["exit_warp"]) if cs.get("exit_warp") else None),
|
|
3292
|
-
walk_calls=walk_calls, reserve_party_band=True)
|
|
3351
|
+
walk_calls=walk_calls, join_tags=join_tags, reserve_party_band=True)
|
|
3293
3352
|
|
|
3294
3353
|
|
|
3295
3354
|
def _inject_verbatim_props(project: FieldProject, eb: bytes, prop_txids=None, *, warnings) -> bytes:
|
|
@@ -3351,6 +3410,11 @@ def _inject_verbatim_gateways(project: FieldProject, eb: bytes, *, warnings) ->
|
|
|
3351
3410
|
zone = gw["zone"]
|
|
3352
3411
|
if len(zone) == 4:
|
|
3353
3412
|
zone = _gw.quad_zone(zone)
|
|
3413
|
+
if gw.get("ate"): # a forced-ATE warp-in, seated below the party band
|
|
3414
|
+
eb = _cutscene.inject_forced_ate(eb, [tuple(p) for p in zone], int(gw["to"]),
|
|
3415
|
+
mode=int(gw.get("ate_mode", _cutscene.ATE_DEFAULT_MODE)),
|
|
3416
|
+
reserve_party_band=True)
|
|
3417
|
+
continue
|
|
3354
3418
|
gf, gs = _gate_of(gw)
|
|
3355
3419
|
eb = _gw.inject_gateway(eb, int(gw["to"]), entrance=int(gw.get("entrance", 0)),
|
|
3356
3420
|
zone=[tuple(p) for p in zone], gate_flag=gf, gate_require_set=gs,
|
|
@@ -3391,12 +3455,14 @@ def build_script(project: FieldProject, lang: str, dialogue_txids: dict,
|
|
|
3391
3455
|
control_value: int = -1, event_txids: dict | None = None,
|
|
3392
3456
|
cutscene_txids: list | None = None, walkmesh=None,
|
|
3393
3457
|
choice_txids: dict | None = None, on_entry_txids: dict | None = None,
|
|
3394
|
-
ate_txids: dict | None = None, chest_txids: dict | None = None
|
|
3458
|
+
ate_txids: dict | None = None, chest_txids: dict | None = None,
|
|
3459
|
+
gateway_txids: dict | None = None) -> bytes:
|
|
3395
3460
|
"""Build one language's .eb by applying the project's content to the blank field."""
|
|
3396
3461
|
_auto = _FlagAlloc(getattr(project, "flag_base", None))
|
|
3397
3462
|
event_txids = event_txids or {}
|
|
3398
3463
|
cutscene_txids = cutscene_txids or []
|
|
3399
3464
|
choice_txids = choice_txids or {}
|
|
3465
|
+
gateway_txids = gateway_txids or {}
|
|
3400
3466
|
on_entry_txids = on_entry_txids or {}
|
|
3401
3467
|
ate_txids = ate_txids or {}
|
|
3402
3468
|
chest_txids = chest_txids or {}
|
|
@@ -3557,10 +3623,15 @@ def build_script(project: FieldProject, lang: str, dialogue_txids: dict,
|
|
|
3557
3623
|
|
|
3558
3624
|
# gateways
|
|
3559
3625
|
gw_names = _story_names(project) # [[flag]] name -> index, for set_flags resolution
|
|
3560
|
-
for gw in project.raw.get("gateway", []):
|
|
3626
|
+
for gi, gw in enumerate(project.raw.get("gateway", [])):
|
|
3561
3627
|
zone = gw["zone"]
|
|
3562
3628
|
if len(zone) == 4:
|
|
3563
3629
|
zone = _gw.quad_zone(zone)
|
|
3630
|
+
if gw.get("ate"): # a forced-ATE warp-in: grey banner WARNING + title, then warp
|
|
3631
|
+
eb = _cutscene.inject_forced_ate(eb, [tuple(p) for p in zone], int(gw["to"]),
|
|
3632
|
+
mode=int(gw.get("ate_mode", _cutscene.ATE_DEFAULT_MODE)),
|
|
3633
|
+
title_txid=gateway_txids.get(gi))
|
|
3634
|
+
continue
|
|
3564
3635
|
gf, gs = _gate_of(gw)
|
|
3565
3636
|
eb = _gw.inject_gateway(eb, int(gw["to"]), entrance=int(gw.get("entrance", 0)),
|
|
3566
3637
|
zone=[tuple(p) for p in zone], gate_flag=gf, gate_require_set=gs,
|
|
@@ -3705,29 +3776,16 @@ def build_script(project: FieldProject, lang: str, dialogue_txids: dict,
|
|
|
3705
3776
|
if _auto.base is not None and "flag" not in cs and cs.get("once", True):
|
|
3706
3777
|
c_fidx = _auto.cutscene() # campaign: pack into this member's flag block
|
|
3707
3778
|
# walk beats can't run inline (base Walk acts on the EXECUTING object; there's no targeted WalkEx),
|
|
3708
|
-
# so generate a per-actor walk-choreography TAG on the actor's own [[npc]] entry and RunScript into
|
|
3709
|
-
#
|
|
3710
|
-
walk_calls =
|
|
3711
|
-
if any("walk" in s for s in c_steps):
|
|
3712
|
-
from .eb import edit as _eb_edit
|
|
3713
|
-
move_reg = _position_registry(project)
|
|
3714
|
-
next_tag = {} # actor name -> next free walk tag
|
|
3715
|
-
for i, s in enumerate(c_steps):
|
|
3716
|
-
if "walk" not in s:
|
|
3717
|
-
continue
|
|
3718
|
-
slot = npc_slots.get(s.get("actor")) # validated to be a real NPC actor (not player)
|
|
3719
|
-
pt = _resolve_point(s["walk"], move_reg)
|
|
3720
|
-
t = next_tag.get(s["actor"], _conductor.WALK_TAG_BASE)
|
|
3721
|
-
next_tag[s["actor"]] = t + 1
|
|
3722
|
-
eb = _eb_edit.add_function(eb, slot, t, _conductor.walk_tag_body(pt[0], pt[1], s.get("speed")))
|
|
3723
|
-
walk_calls[i] = (slot, t)
|
|
3779
|
+
# so generate a per-actor walk-choreography TAG on the actor's own [[npc]] entry and RunScript into it
|
|
3780
|
+
# (animates in the actor's context, blocks until arrival); a parallel walk also gets a join tag.
|
|
3781
|
+
eb, walk_calls, join_tags = _gen_conductor_walk_tags(project, eb, c_steps, npc_slots)
|
|
3724
3782
|
eb = _conductor.inject_conductor(
|
|
3725
3783
|
eb, c_steps, npc_slots, cutscene_txids,
|
|
3726
3784
|
once_flag=(c_fidx if cs.get("once", True) else None), flag_class=c_fclass,
|
|
3727
3785
|
warmup=int(cs.get("warmup", _cutscene.DEFAULT_WARMUP)),
|
|
3728
3786
|
owns_control=bool(cs.get("owns_control", True)),
|
|
3729
3787
|
exit_warp=(int(cs["exit_warp"]) if cs.get("exit_warp") else None),
|
|
3730
|
-
say_flags=cs_say_flags, walk_calls=walk_calls)
|
|
3788
|
+
say_flags=cs_say_flags, walk_calls=walk_calls, join_tags=join_tags)
|
|
3731
3789
|
|
|
3732
3790
|
# cutscene (narration, no actor): an ordered, control-locked sequence on entry (once), run as a
|
|
3733
3791
|
# standalone director code entry. Steps = say / wait / set_flag. An ACTOR cutscene was already
|
|
@@ -4133,17 +4191,40 @@ def _validate_conductor(project, cs, problems):
|
|
|
4133
4191
|
_animations.resolve(token, a)
|
|
4134
4192
|
except ValueError as e:
|
|
4135
4193
|
problems.append(f"[cutscene] step {k}: {e}")
|
|
4136
|
-
if act == "walk":
|
|
4137
|
-
|
|
4138
|
-
problems.append(f"[cutscene] step {k}: walk is not yet supported for \"player\" (only [[npc]] "
|
|
4139
|
-
f"actors can walk in a cutscene for now); use turn/anim/say on the player.")
|
|
4140
|
-
try:
|
|
4194
|
+
if act == "walk": # walk on "player" runs in the player's own
|
|
4195
|
+
try: # entry (uid 250); an [[npc]] in its slot entry
|
|
4141
4196
|
_resolve_point(s["walk"], move_reg) # [x, z] or a known marker/NPC name
|
|
4142
4197
|
except ValueError as e:
|
|
4143
4198
|
problems.append(f"[cutscene] step {k}: {e}")
|
|
4144
4199
|
t = s.get("tail")
|
|
4145
4200
|
if t is not None and t not in _text.TAIL_CODES:
|
|
4146
4201
|
problems.append(f"[cutscene] step {k} tail {t!r} is not a valid TAIL code")
|
|
4202
|
+
# parallel beats (with_prev): a step marked with_prev runs together with the preceding beat. Only
|
|
4203
|
+
# walk/anim/turn can run in parallel (say/wait/set_flag are sequential barriers), the group leader
|
|
4204
|
+
# must be one of those too, and no actor may act twice in a group (it has one execution context).
|
|
4205
|
+
if steps[0].get("with_prev"):
|
|
4206
|
+
problems.append("[cutscene] step 0 can't have with_prev = true (nothing precedes it)")
|
|
4207
|
+
_par_ok = ("walk", "anim", "turn")
|
|
4208
|
+
for g in _conductor.group_parallel(steps):
|
|
4209
|
+
if len(g) < 2:
|
|
4210
|
+
continue
|
|
4211
|
+
lead_k, lead_s = g[0]
|
|
4212
|
+
lead_act = next((key for key in _par_ok if key in lead_s), None)
|
|
4213
|
+
if lead_act is None:
|
|
4214
|
+
problems.append(f"[cutscene] step {lead_k} has a with_prev beat after it but is a "
|
|
4215
|
+
f"say/wait/set_flag (a sequential barrier) -- only walk/anim/turn run in parallel")
|
|
4216
|
+
seen = {lead_s.get("actor")} if lead_act else set()
|
|
4217
|
+
for k, s in g[1:]:
|
|
4218
|
+
act = next((key for key in _par_ok if key in s), None)
|
|
4219
|
+
if act is None:
|
|
4220
|
+
problems.append(f"[cutscene] step {k}: only walk/anim/turn can run with_prev "
|
|
4221
|
+
f"(say/wait/set_flag are sequential barriers)")
|
|
4222
|
+
continue
|
|
4223
|
+
who = s.get("actor")
|
|
4224
|
+
if who in seen:
|
|
4225
|
+
problems.append(f"[cutscene] step {k}: actor {who!r} already acts in this parallel group "
|
|
4226
|
+
f"(an actor can't do two things at once)")
|
|
4227
|
+
seen.add(who)
|
|
4147
4228
|
if "exit_warp" in cs:
|
|
4148
4229
|
ew = cs["exit_warp"]
|
|
4149
4230
|
if not (isinstance(ew, int) and not isinstance(ew, bool) and ew > 0):
|
|
@@ -4432,8 +4513,9 @@ def _wrap_width(project: FieldProject):
|
|
|
4432
4513
|
|
|
4433
4514
|
def collect_text(project: FieldProject):
|
|
4434
4515
|
"""Return (mes_body, npc_txids, event_txids, cutscene_txids, choice_txids, on_entry_txids, ate_txids,
|
|
4435
|
-
chest_txids). All field text (NPC dialogue, event messages, cutscene 'say' lines, choice
|
|
4436
|
-
replies, on-entry messages, the ATE menu, chest "Received X" boxes
|
|
4516
|
+
chest_txids, gateway_txids). All field text (NPC dialogue, event messages, cutscene 'say' lines, choice
|
|
4517
|
+
prompts + replies, on-entry messages, the ATE menu, chest "Received X" boxes, forced-ATE gateway titles)
|
|
4518
|
+
shares one .mes block, in that order
|
|
4437
4519
|
(so a field with no events/cutscene/choices/on_entry/ate/chests is byte-identical to the old layout).
|
|
4438
4520
|
``cutscene_txids`` is a list (one per 'say' step); ``choice_txids[c]`` = ``{"prompt": id, "replies":
|
|
4439
4521
|
{opt_index: id}}``; ``on_entry_txids[k]`` = the txid of hook ``k``'s message (only for hooks that have
|
|
@@ -4521,8 +4603,20 @@ def collect_text(project: FieldProject):
|
|
|
4521
4603
|
for k, ch in enumerate(project.raw.get("chest", [])):
|
|
4522
4604
|
text, strt, tail = _chest_received_box(ch)
|
|
4523
4605
|
ch_pos[k] = _add_raw(text, ch.get("tail") or tail, strt=strt)
|
|
4606
|
+
# forced-ATE gateway titles: the winATE-captioned, CENTERED "title window" a `[[gateway]] ate = true` shows
|
|
4607
|
+
# as it warps. Geometry verified vs real grey ATEs 956/2211: `[STRT=W,1][IMME][CENT=W]title` -- the engine
|
|
4608
|
+
# auto-centers a system window from its [STRT] width (like the chest box) + [CENT] centers the text + [IMME]
|
|
4609
|
+
# pops it fully drawn. W ~ the rendered text width (real STRT ~= text.measure * 7.2). The default dialogue
|
|
4610
|
+
# geometry (10,1)+TAIL=UPR pins it TOP-RIGHT (the reported bug). Added LAST (after chests) -> byte-identical
|
|
4611
|
+
# for a field without one.
|
|
4612
|
+
gw_pos = {}
|
|
4613
|
+
for gi, gw in enumerate(project.raw.get("gateway", [])):
|
|
4614
|
+
if gw.get("ate") and gw.get("ate_title"):
|
|
4615
|
+
_title = str(gw["ate_title"])
|
|
4616
|
+
_w = max(8, round(_text.measure(_title) * 7.2)) # ~ the FF9 STRT width of the rendered title
|
|
4617
|
+
gw_pos[gi] = _add_raw(f"[IMME][CENT={_w}]{_title}", "", strt=(_w, 1)) # NO tail -> the real ATE's true centre
|
|
4524
4618
|
if not lines:
|
|
4525
|
-
return "", {}, {}, [], {}, {}, {}, {}
|
|
4619
|
+
return "", {}, {}, [], {}, {}, {}, {}, {}
|
|
4526
4620
|
body, mapping = _text.build_mes(lines, start_txid=_text.DEFAULT_BASE_TXID, tails=tails, strts=strts)
|
|
4527
4621
|
npc_txids = {i: mapping[p] for i, p in npc_pos.items()}
|
|
4528
4622
|
event_txids = {j: mapping[p] for j, p in ev_pos.items()}
|
|
@@ -4536,7 +4630,9 @@ def collect_text(project: FieldProject):
|
|
|
4536
4630
|
"replies": {oi: mapping[p] for oi, p in ate_reply_pos.items()}}
|
|
4537
4631
|
if ate_prompt_pos is not None else {})
|
|
4538
4632
|
chest_txids = {k: mapping[p] for k, p in ch_pos.items()}
|
|
4539
|
-
|
|
4633
|
+
gateway_txids = {gi: mapping[p] for gi, p in gw_pos.items()} # forced-ATE title-window txids (by gw index)
|
|
4634
|
+
return (body, npc_txids, event_txids, cutscene_txids, choice_txids, on_entry_txids, ate_txids,
|
|
4635
|
+
chest_txids, gateway_txids)
|
|
4540
4636
|
|
|
4541
4637
|
|
|
4542
4638
|
# --------------------------------------------------------------------------- the build
|
|
@@ -4717,7 +4813,7 @@ def build_field(project: FieldProject, layout: ModLayout, *, langs=LANGS) -> Fie
|
|
|
4717
4813
|
|
|
4718
4814
|
_autofill_ladder_landing_y(project, cutscene_wmesh) # elevated dismount floors get their real Y
|
|
4719
4815
|
# --- dialogue + per-language script ---
|
|
4720
|
-
mes_body, txids, event_txids, cutscene_txids, choice_txids, on_entry_txids, ate_txids, chest_txids = collect_text(project)
|
|
4816
|
+
mes_body, txids, event_txids, cutscene_txids, choice_txids, on_entry_txids, ate_txids, chest_txids, gateway_txids = collect_text(project)
|
|
4721
4817
|
control_value = resolve_control_value(project, camera)
|
|
4722
4818
|
# faithful text carry: the donor's referenced dialogue, shipped VERBATIM per language and APPENDED after
|
|
4723
4819
|
# the authored block (its own [TXID=>=1000] re-index keeps it disjoint -- authored text + the hut golden
|
|
@@ -4923,7 +5019,7 @@ def build_field(project: FieldProject, layout: ModLayout, *, langs=LANGS) -> Fie
|
|
|
4923
5019
|
eb = build_script(project, lang, txids, control_value, event_txids=event_txids,
|
|
4924
5020
|
cutscene_txids=cutscene_txids, walkmesh=cutscene_wmesh,
|
|
4925
5021
|
choice_txids=choice_txids, on_entry_txids=on_entry_txids,
|
|
4926
|
-
ate_txids=ate_txids, chest_txids=chest_txids)
|
|
5022
|
+
ate_txids=ate_txids, chest_txids=chest_txids, gateway_txids=gateway_txids)
|
|
4927
5023
|
base = mes_body or ""
|
|
4928
5024
|
inplace = base
|
|
4929
5025
|
suffix = ""
|
|
@@ -30,6 +30,7 @@ Subcommands are wired up incrementally as the library lands:
|
|
|
30
30
|
models/scenes/catalog - the Info Hub: browse models (+ their animations), battle scenes, or
|
|
31
31
|
search every reference catalog by name
|
|
32
32
|
extract-templates - regenerate base assets from the user's own FF9 install (no game data shipped)
|
|
33
|
+
setup - one-shot: find the FF9 install, remember it, extract base assets, report Memoria status
|
|
33
34
|
|
|
34
35
|
Anything not yet implemented prints a clear "coming in Phase N" message rather than failing
|
|
35
36
|
with an import error, so the installed console script is always runnable.
|
|
@@ -104,6 +105,80 @@ def _cmd_extract_templates(args: argparse.Namespace) -> int:
|
|
|
104
105
|
return 0
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
def _cmd_setup(args: argparse.Namespace) -> int:
|
|
109
|
+
"""One-shot post-install setup: find the FF9 install, remember it in the user config, regenerate the
|
|
110
|
+
base assets, and report the Memoria engine status. ``--install-engine <zip>`` additionally installs the
|
|
111
|
+
Dream World IX engine bundle (backed up first). Safe to re-run; returns non-zero only so a calling
|
|
112
|
+
script (e.g. the installer) can tell -- it never needs to block."""
|
|
113
|
+
from . import memoria, provision
|
|
114
|
+
from .config import save_game_path
|
|
115
|
+
|
|
116
|
+
# 1) resolve the install, and remember it so future commands need no --game/$FF9_GAME_PATH
|
|
117
|
+
try:
|
|
118
|
+
game = find_game_path(args.game)
|
|
119
|
+
except ConfigError as e:
|
|
120
|
+
print(str(e), file=sys.stderr)
|
|
121
|
+
print("\nOnce you know the path, run: ff9mapkit setup --game \"<path>\"", file=sys.stderr)
|
|
122
|
+
return 2
|
|
123
|
+
print(f"Found FINAL FANTASY IX: {game}")
|
|
124
|
+
try:
|
|
125
|
+
cfg = save_game_path(game)
|
|
126
|
+
print(f" remembered in {cfg} (future commands won't need --game)")
|
|
127
|
+
except OSError as e: # noqa: BLE001 - config write is best-effort
|
|
128
|
+
print(f" (couldn't save the config: {e})", file=sys.stderr)
|
|
129
|
+
|
|
130
|
+
rc = 0
|
|
131
|
+
# 2) regenerate the base assets (the one-time bring-your-own-install step; reads YOUR install)
|
|
132
|
+
if not args.no_extract:
|
|
133
|
+
if provision.templates_present() and not args.force:
|
|
134
|
+
print("Base assets: already extracted (use --force to redo).")
|
|
135
|
+
elif not _has_unitypy():
|
|
136
|
+
print("Base assets: SKIPPED -- UnityPy isn't installed (it reads FF9's assetbundles).\n"
|
|
137
|
+
" Install it: pip install UnityPy (or reinstall ff9mapkit with the [assets] extra)",
|
|
138
|
+
file=sys.stderr)
|
|
139
|
+
rc = 1
|
|
140
|
+
else:
|
|
141
|
+
print("Base assets: regenerating from your install (ff9mapkit ships no game data)...")
|
|
142
|
+
try:
|
|
143
|
+
rep = provision.extract_templates(game=str(game), fixtures=not args.no_fixtures, verbose=True)
|
|
144
|
+
print(f" OK -- {len(rep['verified'])} assets regenerated + verified.")
|
|
145
|
+
except Exception as e: # noqa: BLE001
|
|
146
|
+
print(f" extract-templates failed: {e}", file=sys.stderr)
|
|
147
|
+
rc = 1
|
|
148
|
+
|
|
149
|
+
# 3) Memoria engine status (forked fields need it; novel/from-scratch fields run on stock Memoria)
|
|
150
|
+
st = memoria.memoria_status(game)
|
|
151
|
+
if st["installed"]:
|
|
152
|
+
print("Memoria engine: detected -- forked real fields will work.")
|
|
153
|
+
else:
|
|
154
|
+
print("Memoria engine: NOT detected.")
|
|
155
|
+
print(" From-scratch / BG-borrow fields run on stock Memoria. To play FORKED real fields, install\n"
|
|
156
|
+
" Memoria + the Dream World IX engine bundle (dwix-custom-memoria-*.zip) -- see docs/ENGINE.md,\n"
|
|
157
|
+
" or re-run: ff9mapkit setup --install-engine <bundle.zip>")
|
|
158
|
+
|
|
159
|
+
# 4) opt-in: install the engine bundle (backed up; never touches Memoria.ini)
|
|
160
|
+
if args.install_engine:
|
|
161
|
+
zp = Path(args.install_engine)
|
|
162
|
+
if not zp.is_file():
|
|
163
|
+
print(f"--install-engine: file not found: {zp}", file=sys.stderr)
|
|
164
|
+
return 1
|
|
165
|
+
import datetime # noqa: PLC0415
|
|
166
|
+
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
167
|
+
print(f"Engine bundle: installing {zp.name} (backing up the originals first)...")
|
|
168
|
+
try:
|
|
169
|
+
rep = memoria.install_engine_bundle(game, zp, stamp=stamp)
|
|
170
|
+
print(f" backed up {len(rep['backed_up'])} DLL(s) -> {rep['backup_root']}")
|
|
171
|
+
print(f" installed {len(rep['installed'])} DLL(s) into x64 + x86 Managed.")
|
|
172
|
+
print(" Relaunch FF9 to load it. (Undo: copy the backup DLLs back over the Managed ones.)")
|
|
173
|
+
except Exception as e: # noqa: BLE001
|
|
174
|
+
print(f" engine install failed: {e}", file=sys.stderr)
|
|
175
|
+
return 1
|
|
176
|
+
|
|
177
|
+
if rc == 0:
|
|
178
|
+
print("\nSetup complete. Try 'ff9mapkit doctor', or open the GUI with 'ff9mapkit-workspace'.")
|
|
179
|
+
return rc
|
|
180
|
+
|
|
181
|
+
|
|
107
182
|
def _cmd_disasm(args: argparse.Namespace) -> int:
|
|
108
183
|
from .eb import EbScript
|
|
109
184
|
|
|
@@ -3101,6 +3176,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3101
3176
|
xt.add_argument("--no-fixtures", action="store_true", help="skip the test fixtures (templates only)")
|
|
3102
3177
|
xt.set_defaults(func=_cmd_extract_templates)
|
|
3103
3178
|
|
|
3179
|
+
su = sub.add_parser("setup",
|
|
3180
|
+
help="one-shot post-install setup: find your FF9 install, remember it, extract base "
|
|
3181
|
+
"assets, report Memoria status (--install-engine ZIP to install the engine bundle)")
|
|
3182
|
+
su.add_argument("--install-engine", metavar="ZIP", default=None,
|
|
3183
|
+
help="also install the Dream World IX engine bundle (dwix-custom-memoria-*.zip) -- backs "
|
|
3184
|
+
"up the originals; needed only to play FORKED real fields")
|
|
3185
|
+
su.add_argument("--force", action="store_true", help="re-extract the base assets even if already present")
|
|
3186
|
+
su.add_argument("--no-extract", action="store_true", help="skip the base-asset extraction step")
|
|
3187
|
+
su.add_argument("--no-fixtures", action="store_true", help="skip test fixtures during extraction")
|
|
3188
|
+
su.set_defaults(func=_cmd_setup)
|
|
3189
|
+
|
|
3104
3190
|
return p
|
|
3105
3191
|
|
|
3106
3192
|
|
|
@@ -19,6 +19,7 @@ offline (build into a temp dir, diff against the deployed assets).
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
21
|
import os
|
|
22
|
+
import re
|
|
22
23
|
from dataclasses import dataclass
|
|
23
24
|
from pathlib import Path
|
|
24
25
|
|
|
@@ -60,6 +61,25 @@ def _read_user_config() -> dict:
|
|
|
60
61
|
return {}
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
def save_game_path(game_path: str | os.PathLike) -> Path:
|
|
65
|
+
"""Persist ``game_path`` into the user config (``~/.ff9mapkit.toml``) so later commands resolve the
|
|
66
|
+
install without ``--game`` / ``$FF9_GAME_PATH``. Surgical: rewrites only the ``game_path`` line and
|
|
67
|
+
preserves any other keys/comments. Stores the path with forward slashes so the TOML basic string can
|
|
68
|
+
never trip a backslash escape on Windows (``Path`` reads forward slashes fine). Returns the config path.
|
|
69
|
+
"""
|
|
70
|
+
p = str(Path(game_path).resolve()).replace("\\", "/")
|
|
71
|
+
line = f'game_path = "{p}"'
|
|
72
|
+
existing = USER_CONFIG.read_text(encoding="utf-8") if USER_CONFIG.is_file() else ""
|
|
73
|
+
if re.search(r"(?m)^[ \t]*game_path[ \t]*=", existing):
|
|
74
|
+
new = re.sub(r"(?m)^[ \t]*game_path[ \t]*=.*$", line, existing)
|
|
75
|
+
elif existing.strip():
|
|
76
|
+
new = existing.rstrip("\n") + "\n" + line + "\n"
|
|
77
|
+
else:
|
|
78
|
+
new = line + "\n"
|
|
79
|
+
USER_CONFIG.write_text(new, encoding="utf-8")
|
|
80
|
+
return USER_CONFIG
|
|
81
|
+
|
|
82
|
+
|
|
63
83
|
def find_game_path(explicit: str | os.PathLike | None = None) -> Path:
|
|
64
84
|
"""Resolve the Final Fantasy IX install folder.
|
|
65
85
|
|