ff9mapkit 1.0.0b3__py3-none-any.whl
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/__init__.py +18 -0
- ff9mapkit/__main__.py +36 -0
- ff9mapkit/_animdb.py +2994 -0
- ff9mapkit/_animdb_all.py +14125 -0
- ff9mapkit/_fieldtable.py +1516 -0
- ff9mapkit/_fieldtext.py +845 -0
- ff9mapkit/_held_poses.py +44 -0
- ff9mapkit/_itemdb.py +65 -0
- ff9mapkit/_modeldb.py +725 -0
- ff9mapkit/_narrowmap_data.py +10 -0
- ff9mapkit/_npcparams.py +634 -0
- ff9mapkit/_regen_animdb.py +72 -0
- ff9mapkit/_regen_animdb_all.py +66 -0
- ff9mapkit/_regen_fieldtable.py +95 -0
- ff9mapkit/_regen_fieldtext.py +66 -0
- ff9mapkit/_regen_modeldb.py +67 -0
- ff9mapkit/_regen_npcparams.py +123 -0
- ff9mapkit/_regen_scenedb.py +57 -0
- ff9mapkit/_scenedb.py +869 -0
- ff9mapkit/abilities.py +225 -0
- ff9mapkit/animations.py +120 -0
- ff9mapkit/archetypes.py +218 -0
- ff9mapkit/areatitle.py +76 -0
- ff9mapkit/battle/__init__.py +21 -0
- ff9mapkit/battle/abilityfeatures.py +294 -0
- ff9mapkit/battle/actiondelta.py +441 -0
- ff9mapkit/battle/aiauthor.py +305 -0
- ff9mapkit/battle/ailint.py +140 -0
- ff9mapkit/battle/aipatch.py +175 -0
- ff9mapkit/battle/battleai.py +148 -0
- ff9mapkit/battle/battlecsv.py +390 -0
- ff9mapkit/battle/battlepatch.py +395 -0
- ff9mapkit/battle/build.py +558 -0
- ff9mapkit/battle/camera_codec.py +332 -0
- ff9mapkit/battle/camera_data.py +128 -0
- ff9mapkit/battle/characterdelta.py +789 -0
- ff9mapkit/battle/event_data.py +72 -0
- ff9mapkit/battle/extract.py +540 -0
- ff9mapkit/battle/fbx.py +223 -0
- ff9mapkit/battle/reskin.py +149 -0
- ff9mapkit/battle/scene_codec.py +314 -0
- ff9mapkit/battle/scene_data.py +369 -0
- ff9mapkit/battle/scenelint.py +125 -0
- ff9mapkit/battle/seqasm.py +131 -0
- ff9mapkit/battle/seqauthor.py +220 -0
- ff9mapkit/battle/seqcodec.py +300 -0
- ff9mapkit/battle/seqdis.py +106 -0
- ff9mapkit/battle/seqpatch.py +137 -0
- ff9mapkit/battle_bgm.py +133 -0
- ff9mapkit/binutils.py +60 -0
- ff9mapkit/build.py +5445 -0
- ff9mapkit/campaign.py +1276 -0
- ff9mapkit/catalog.py +316 -0
- ff9mapkit/chain.py +358 -0
- ff9mapkit/cli.py +3114 -0
- ff9mapkit/config.py +360 -0
- ff9mapkit/content/__init__.py +13 -0
- ff9mapkit/content/areatitle.py +36 -0
- ff9mapkit/content/ate.py +118 -0
- ff9mapkit/content/camera.py +123 -0
- ff9mapkit/content/chest.py +186 -0
- ff9mapkit/content/choice.py +163 -0
- ff9mapkit/content/conductor.py +217 -0
- ff9mapkit/content/cutscene.py +290 -0
- ff9mapkit/content/encounter.py +41 -0
- ff9mapkit/content/entry_settle.py +50 -0
- ff9mapkit/content/equipment.py +93 -0
- ff9mapkit/content/event.py +191 -0
- ff9mapkit/content/gateway.py +101 -0
- ff9mapkit/content/inventory.py +59 -0
- ff9mapkit/content/itemdata.py +644 -0
- ff9mapkit/content/itemtext.py +168 -0
- ff9mapkit/content/jump.py +114 -0
- ff9mapkit/content/ladder.py +633 -0
- ff9mapkit/content/movement.py +53 -0
- ff9mapkit/content/music.py +97 -0
- ff9mapkit/content/npc.py +348 -0
- ff9mapkit/content/object.py +340 -0
- ff9mapkit/content/onentry.py +135 -0
- ff9mapkit/content/party.py +111 -0
- ff9mapkit/content/pathfind.py +138 -0
- ff9mapkit/content/platform.py +314 -0
- ff9mapkit/content/player.py +168 -0
- ff9mapkit/content/prop.py +75 -0
- ff9mapkit/content/region.py +340 -0
- ff9mapkit/content/reinit.py +59 -0
- ff9mapkit/content/savepoint.py +90 -0
- ff9mapkit/content/shop.py +178 -0
- ff9mapkit/content/sps_trigger.py +66 -0
- ff9mapkit/content/startup.py +71 -0
- ff9mapkit/content/synthesis.py +106 -0
- ff9mapkit/content/text.py +183 -0
- ff9mapkit/content/textcarry.py +290 -0
- ff9mapkit/content/verbatim.py +86 -0
- ff9mapkit/content/walkmesh_hotfix.py +38 -0
- ff9mapkit/data/__init__.py +48 -0
- ff9mapkit/data/_regen_provenance.py +142 -0
- ff9mapkit/data/provenance/blank.es.patch +1 -0
- ff9mapkit/data/provenance/blank.fr.patch +1 -0
- ff9mapkit/data/provenance/blank.gr.patch +1 -0
- ff9mapkit/data/provenance/blank.it.patch +1 -0
- ff9mapkit/data/provenance/blank.jp.patch +1 -0
- ff9mapkit/data/provenance/blank.uk.patch +1 -0
- ff9mapkit/data/provenance/blank.us.patch +1 -0
- ff9mapkit/data/provenance/manifest.json +65 -0
- ff9mapkit/data/provenance/region_template.patch +1 -0
- ff9mapkit/data/reference_arcs.toml +89 -0
- ff9mapkit/data/region_catalog.toml +593 -0
- ff9mapkit/deploystack.py +358 -0
- ff9mapkit/dialogue.py +803 -0
- ff9mapkit/eb/__init__.py +12 -0
- ff9mapkit/eb/_exprtable.py +59 -0
- ff9mapkit/eb/_membertable.py +38 -0
- ff9mapkit/eb/_optables.py +537 -0
- ff9mapkit/eb/_regen_optables.py +76 -0
- ff9mapkit/eb/cmdasm.py +323 -0
- ff9mapkit/eb/disasm.py +332 -0
- ff9mapkit/eb/edit.py +439 -0
- ff9mapkit/eb/exprasm.py +158 -0
- ff9mapkit/eb/model.py +178 -0
- ff9mapkit/eb/opcodes.py +463 -0
- ff9mapkit/eblint.py +177 -0
- ff9mapkit/editor/__init__.py +20 -0
- ff9mapkit/editor/app.py +950 -0
- ff9mapkit/editor/battle_forms.py +240 -0
- ff9mapkit/editor/breadcrumb.py +89 -0
- ff9mapkit/editor/dialogs.py +116 -0
- ff9mapkit/editor/feedback.py +208 -0
- ff9mapkit/editor/forms.py +632 -0
- ff9mapkit/editor/graphview.py +350 -0
- ff9mapkit/editor/jobs.py +342 -0
- ff9mapkit/editor/model.py +243 -0
- ff9mapkit/editor/picker.py +120 -0
- ff9mapkit/editor/theme.py +212 -0
- ff9mapkit/eventscan.py +1441 -0
- ff9mapkit/extract.py +2279 -0
- ff9mapkit/flags.py +693 -0
- ff9mapkit/forkreport.py +1383 -0
- ff9mapkit/hub.py +477 -0
- ff9mapkit/idgated.py +101 -0
- ff9mapkit/infohub.py +580 -0
- ff9mapkit/items.py +63 -0
- ff9mapkit/itemstats.py +346 -0
- ff9mapkit/journey.py +1902 -0
- ff9mapkit/keyitems.py +93 -0
- ff9mapkit/logic_add.py +632 -0
- ff9mapkit/logic_edit.py +728 -0
- ff9mapkit/logic_map.py +526 -0
- ff9mapkit/pack.py +175 -0
- ff9mapkit/playerswap.py +231 -0
- ff9mapkit/prop_archetypes.py +228 -0
- ff9mapkit/provision.py +282 -0
- ff9mapkit/refarc.py +825 -0
- ff9mapkit/save.py +337 -0
- ff9mapkit/save_items.py +1673 -0
- ff9mapkit/scene/__init__.py +11 -0
- ff9mapkit/scene/arena.py +63 -0
- ff9mapkit/scene/bgart.py +140 -0
- ff9mapkit/scene/bgi.py +732 -0
- ff9mapkit/scene/bgs.py +174 -0
- ff9mapkit/scene/bgx.py +185 -0
- ff9mapkit/scene/cam.py +345 -0
- ff9mapkit/scene/guide.py +311 -0
- ff9mapkit/scene/paint.py +506 -0
- ff9mapkit/scene/placeholder.py +107 -0
- ff9mapkit/sjbinary.py +285 -0
- ff9mapkit/sps/__init__.py +17 -0
- ff9mapkit/sps/author.py +294 -0
- ff9mapkit/sps/catalog.py +88 -0
- ff9mapkit/sps/codec.py +264 -0
- ff9mapkit/sps/edit.py +184 -0
- ff9mapkit/sps/lint.py +58 -0
- ff9mapkit/sps/render.py +116 -0
- ff9mapkit/sps/templates.py +47 -0
- ff9mapkit/sps/texture.py +131 -0
- ff9mapkit/walkmesh_hotfixes.py +163 -0
- ff9mapkit/workspace/__init__.py +18 -0
- ff9mapkit/workspace/battledoc.py +985 -0
- ff9mapkit/workspace/builddoc.py +607 -0
- ff9mapkit/workspace/forms_qt.py +586 -0
- ff9mapkit/workspace/importdoc.py +665 -0
- ff9mapkit/workspace/mapview.py +131 -0
- ff9mapkit/workspace/palette.py +85 -0
- ff9mapkit/workspace/savedoc.py +664 -0
- ff9mapkit/workspace/shell.py +6907 -0
- ff9mapkit/workspace/style.py +105 -0
- ff9mapkit/workspace/tuningdialog.py +223 -0
- ff9mapkit-1.0.0b3.dist-info/METADATA +155 -0
- ff9mapkit-1.0.0b3.dist-info/RECORD +193 -0
- ff9mapkit-1.0.0b3.dist-info/WHEEL +5 -0
- ff9mapkit-1.0.0b3.dist-info/entry_points.txt +5 -0
- ff9mapkit-1.0.0b3.dist-info/licenses/LICENSE +31 -0
- ff9mapkit-1.0.0b3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Full read-only codec for a BTL_SCENE ``dbfile0000.raw16`` -- the per-scene binary that holds every
|
|
2
|
+
enemy's stats / affinities / rewards + the spawn patterns + the enemy attack table.
|
|
3
|
+
|
|
4
|
+
This is the *parse* side of battle tuning (the SCANNER): it reads the WHOLE scene into named records so a
|
|
5
|
+
tool (or the offline lint suite) can SEE every field -- not just the ~9 that :mod:`scene_data` byte-patches.
|
|
6
|
+
:func:`serialize_scene` re-emits byte-for-byte, including the engine-ignored TAIL after the attack block, so
|
|
7
|
+
``serialize_scene(parse_scene(x)) == x`` is the golden round-trip that PROVES the offset map (and converts
|
|
8
|
+
the kit's "copy-identity" forks into "codec-identity").
|
|
9
|
+
|
|
10
|
+
Layout authority = Memoria ``Global/BTL_SCENE.cs`` (the ``ReadBattleScene`` BinaryReader order) and
|
|
11
|
+
``Global/SB2/SB2_MON_PARM.cs``. All multi-byte fields are little-endian. NOTE the disk widths differ from the
|
|
12
|
+
runtime struct: e.g. MaxHP/PhysicalDefence are widened to UInt32/Int32 in memory but are u16/u8 ON DISK
|
|
13
|
+
(`ReadUInt16`/`ReadByte`) -- this codec follows the DISK widths.
|
|
14
|
+
|
|
15
|
+
This file is a pure codec (no Square-Enix bytes); it reads bytes the caller supplies (a forked donor read
|
|
16
|
+
live from the install) and ships nothing.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import struct
|
|
21
|
+
from dataclasses import dataclass, field as _dcfield
|
|
22
|
+
|
|
23
|
+
_HDR = 8
|
|
24
|
+
_PAT = 56
|
|
25
|
+
_MON = 116
|
|
26
|
+
_PUT = 12
|
|
27
|
+
_ATK = 16
|
|
28
|
+
|
|
29
|
+
# ---- struct formats (little-endian; verified to sum to the fixed record sizes) -------------------------
|
|
30
|
+
# SB2_PATTERN (56B): Rate,MonsterCount,Camera,Pad0 (4B) + AP (u32) + 4x SB2_PUT (12B each).
|
|
31
|
+
_PAT_FMT = "<4BI" + "4B4h" * 4
|
|
32
|
+
# SB2_MON_PARM (116B): the exact ReadBattleScene order (BTL_SCENE.cs:54-122).
|
|
33
|
+
_MON_FMT = (
|
|
34
|
+
"<3I" # ResistStatus, AutoStatus, InitialStatus (u32 BattleStatus masks) @0
|
|
35
|
+
"4H" # MaxHP, MaxMP, WinGil, WinExp @12
|
|
36
|
+
"4B" # WinItems[4] (drop item ids; 255=none) @20
|
|
37
|
+
"4B" # StealItems[4] @24
|
|
38
|
+
"H" # Radius @28
|
|
39
|
+
"h" # Geo (i16, model) @30
|
|
40
|
+
"6H" # Mot[6] (movement/attack anim ids) @32
|
|
41
|
+
"2H" # Mesh[2] @44
|
|
42
|
+
"H" # Flags (per-enemy MON flags) @48
|
|
43
|
+
"H" # AP (per-type; the GAMEPLAY AP is the pattern AP) @50
|
|
44
|
+
"8B" # Element: Speed,Strength,Magic,Spirit, pad,trans,cur_capa,max_capa @52
|
|
45
|
+
"5B" # GuardElement,AbsorbElement,HalfElement,WeakElement, Level @60
|
|
46
|
+
"7B" # Category,HitRate,PhysicalDefence,PhysicalEvade,MagicalDefence,MagicalEvade,BlueMagic @65
|
|
47
|
+
"4B" # Bone[4] @72
|
|
48
|
+
"H" # DieSfx @76
|
|
49
|
+
"2B" # Konran, MesCnt @78
|
|
50
|
+
"6B" # IconBone[6] @80
|
|
51
|
+
"6b" # IconY[6] (sbyte) @86
|
|
52
|
+
"6b" # IconZ[6] (sbyte) @92
|
|
53
|
+
"3H" # StartSfx, ShadowX, ShadowZ @98
|
|
54
|
+
"2B" # ShadowBone, WinCard @104
|
|
55
|
+
"2h" # ShadowOfsX, ShadowOfsZ (i16) @106
|
|
56
|
+
"2B" # ShadowBone2, Pad0 @110
|
|
57
|
+
"2H" # Pad1, Pad2 @112
|
|
58
|
+
)
|
|
59
|
+
# AA_DATA (16B): packed info (u32) + 8 single bytes + Vfx2 (u16) + Name (u16).
|
|
60
|
+
_ATK_FMT = "<I8B2H"
|
|
61
|
+
|
|
62
|
+
assert struct.calcsize(_PAT_FMT) == _PAT
|
|
63
|
+
assert struct.calcsize(_MON_FMT) == _MON
|
|
64
|
+
assert struct.calcsize(_ATK_FMT) == _ATK
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SceneCodecError(ValueError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Put:
|
|
73
|
+
"""SB2_PUT -- one formation slot's enemy type + placement."""
|
|
74
|
+
type_no: int
|
|
75
|
+
flags: int
|
|
76
|
+
pease: int
|
|
77
|
+
pad: int
|
|
78
|
+
x: int
|
|
79
|
+
y: int
|
|
80
|
+
z: int
|
|
81
|
+
rot: int
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def targetable(self) -> bool:
|
|
85
|
+
return bool(self.flags & 1) # FLG_TARGETABLE
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class Pattern:
|
|
90
|
+
"""SB2_PATTERN -- one formation (spawn weight, count, camera, AP, 4 placements)."""
|
|
91
|
+
rate: int
|
|
92
|
+
monster_count: int
|
|
93
|
+
camera: int
|
|
94
|
+
pad0: int
|
|
95
|
+
ap: int
|
|
96
|
+
puts: list # 4x Put
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class MonParm:
|
|
101
|
+
"""SB2_MON_PARM -- one enemy TYPE's full record (every disk field, named).
|
|
102
|
+
|
|
103
|
+
The gameplay-relevant fields are scalars; cosmetic/pad/array fields are kept verbatim (so the record
|
|
104
|
+
round-trips) but rarely interesting. Element masks (`guard/absorb/half/weak_element`) and the status
|
|
105
|
+
masks (`resist/auto/initial_status`) are raw ints -- decode with :mod:`scene_data` helpers."""
|
|
106
|
+
resist_status: int
|
|
107
|
+
auto_status: int
|
|
108
|
+
initial_status: int
|
|
109
|
+
hp: int
|
|
110
|
+
mp: int
|
|
111
|
+
gil: int
|
|
112
|
+
exp: int
|
|
113
|
+
drop: tuple # WinItems[4]
|
|
114
|
+
steal: tuple # StealItems[4]
|
|
115
|
+
radius: int
|
|
116
|
+
geo: int
|
|
117
|
+
mot: tuple # [6]
|
|
118
|
+
mesh: tuple # [2]
|
|
119
|
+
flags: int
|
|
120
|
+
ap: int
|
|
121
|
+
speed: int
|
|
122
|
+
strength: int
|
|
123
|
+
magic: int
|
|
124
|
+
spirit: int
|
|
125
|
+
elem_pad: int
|
|
126
|
+
trans: int
|
|
127
|
+
cur_capa: int
|
|
128
|
+
max_capa: int
|
|
129
|
+
guard_element: int
|
|
130
|
+
absorb_element: int
|
|
131
|
+
half_element: int
|
|
132
|
+
weak_element: int
|
|
133
|
+
level: int
|
|
134
|
+
category: int
|
|
135
|
+
hit_rate: int
|
|
136
|
+
phys_def: int
|
|
137
|
+
phys_evade: int
|
|
138
|
+
mag_def: int
|
|
139
|
+
mag_evade: int
|
|
140
|
+
blue_magic: int
|
|
141
|
+
bone: tuple # [4]
|
|
142
|
+
die_sfx: int
|
|
143
|
+
konran: int
|
|
144
|
+
mes_cnt: int
|
|
145
|
+
icon_bone: tuple # [6]
|
|
146
|
+
icon_y: tuple # [6] sbyte
|
|
147
|
+
icon_z: tuple # [6] sbyte
|
|
148
|
+
start_sfx: int
|
|
149
|
+
shadow_x: int
|
|
150
|
+
shadow_z: int
|
|
151
|
+
shadow_bone: int
|
|
152
|
+
win_card: int
|
|
153
|
+
shadow_ofs_x: int
|
|
154
|
+
shadow_ofs_z: int
|
|
155
|
+
shadow_bone2: int
|
|
156
|
+
pad0: int
|
|
157
|
+
pad1: int
|
|
158
|
+
pad2: int
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def unpack(cls, buf, off=0) -> "MonParm":
|
|
162
|
+
t = struct.unpack_from(_MON_FMT, buf, off)
|
|
163
|
+
i = iter(t)
|
|
164
|
+
n = lambda: next(i)
|
|
165
|
+
take = lambda k: tuple(next(i) for _ in range(k))
|
|
166
|
+
return cls(
|
|
167
|
+
resist_status=n(), auto_status=n(), initial_status=n(),
|
|
168
|
+
hp=n(), mp=n(), gil=n(), exp=n(),
|
|
169
|
+
drop=take(4), steal=take(4),
|
|
170
|
+
radius=n(), geo=n(), mot=take(6), mesh=take(2),
|
|
171
|
+
flags=n(), ap=n(),
|
|
172
|
+
speed=n(), strength=n(), magic=n(), spirit=n(),
|
|
173
|
+
elem_pad=n(), trans=n(), cur_capa=n(), max_capa=n(),
|
|
174
|
+
guard_element=n(), absorb_element=n(), half_element=n(), weak_element=n(),
|
|
175
|
+
level=n(), category=n(), hit_rate=n(),
|
|
176
|
+
phys_def=n(), phys_evade=n(), mag_def=n(), mag_evade=n(), blue_magic=n(),
|
|
177
|
+
bone=take(4), die_sfx=n(), konran=n(), mes_cnt=n(),
|
|
178
|
+
icon_bone=take(6), icon_y=take(6), icon_z=take(6),
|
|
179
|
+
start_sfx=n(), shadow_x=n(), shadow_z=n(),
|
|
180
|
+
shadow_bone=n(), win_card=n(), shadow_ofs_x=n(), shadow_ofs_z=n(),
|
|
181
|
+
shadow_bone2=n(), pad0=n(), pad1=n(), pad2=n())
|
|
182
|
+
|
|
183
|
+
def pack(self) -> bytes:
|
|
184
|
+
return struct.pack(
|
|
185
|
+
_MON_FMT, self.resist_status, self.auto_status, self.initial_status,
|
|
186
|
+
self.hp, self.mp, self.gil, self.exp, *self.drop, *self.steal,
|
|
187
|
+
self.radius, self.geo, *self.mot, *self.mesh, self.flags, self.ap,
|
|
188
|
+
self.speed, self.strength, self.magic, self.spirit,
|
|
189
|
+
self.elem_pad, self.trans, self.cur_capa, self.max_capa,
|
|
190
|
+
self.guard_element, self.absorb_element, self.half_element, self.weak_element,
|
|
191
|
+
self.level, self.category, self.hit_rate,
|
|
192
|
+
self.phys_def, self.phys_evade, self.mag_def, self.mag_evade, self.blue_magic,
|
|
193
|
+
*self.bone, self.die_sfx, self.konran, self.mes_cnt,
|
|
194
|
+
*self.icon_bone, *self.icon_y, *self.icon_z,
|
|
195
|
+
self.start_sfx, self.shadow_x, self.shadow_z,
|
|
196
|
+
self.shadow_bone, self.win_card, self.shadow_ofs_x, self.shadow_ofs_z,
|
|
197
|
+
self.shadow_bone2, self.pad0, self.pad1, self.pad2)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class Attack:
|
|
202
|
+
"""AA_DATA -- one enemy attack (the per-scene atk[] entries; NOT Actions.csv)."""
|
|
203
|
+
info: int # packed BattleCommandInfo (Target/VfxIndex/ForDead/... )
|
|
204
|
+
script_id: int
|
|
205
|
+
power: int
|
|
206
|
+
elements: int
|
|
207
|
+
rate: int
|
|
208
|
+
category: int
|
|
209
|
+
add_status: int # StatusSetId
|
|
210
|
+
mp: int
|
|
211
|
+
type: int
|
|
212
|
+
vfx2: int
|
|
213
|
+
name: int
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass
|
|
217
|
+
class Scene:
|
|
218
|
+
"""A parsed BTL_SCENE. ``head`` keeps the 8 header bytes verbatim (Ver/counts/Flags/pad); ``tail`` is
|
|
219
|
+
every byte after the attack block (overwhelmingly zero + engine-ignored, kept for exact re-emit)."""
|
|
220
|
+
head: bytes
|
|
221
|
+
patterns: list # list[Pattern]
|
|
222
|
+
monsters: list # list[MonParm]
|
|
223
|
+
attacks: list # list[Attack]
|
|
224
|
+
tail: bytes = b""
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def pat_count(self) -> int:
|
|
228
|
+
return self.head[1]
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def typ_count(self) -> int:
|
|
232
|
+
return self.head[2]
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def atk_count(self) -> int:
|
|
236
|
+
return self.head[3]
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def back_attack(self) -> bool:
|
|
240
|
+
return bool(self.scene_flags & 2)
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def can_escape(self) -> bool:
|
|
244
|
+
return not (self.scene_flags & 32) # Runaway = (flags & 32) == 0
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def no_exp(self) -> bool:
|
|
248
|
+
return bool(self.scene_flags & 8)
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def preemptive(self) -> bool:
|
|
252
|
+
return bool(self.scene_flags & 1) # SpecialStart
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def scene_flags(self) -> int:
|
|
256
|
+
return struct.unpack_from("<H", self.head, 4)[0]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def parse_scene(raw16: bytes) -> Scene:
|
|
260
|
+
"""Parse a whole ``dbfile0000.raw16`` into a :class:`Scene` (read-only scanner)."""
|
|
261
|
+
if len(raw16) < _HDR:
|
|
262
|
+
raise SceneCodecError("raw16 too short for a header")
|
|
263
|
+
head = bytes(raw16[:_HDR])
|
|
264
|
+
pat_count, typ_count, atk_count = head[1], head[2], head[3]
|
|
265
|
+
need = _HDR + _PAT * pat_count + _MON * typ_count + _ATK * atk_count
|
|
266
|
+
if len(raw16) < need:
|
|
267
|
+
raise SceneCodecError(f"raw16 truncated: need {need} bytes for "
|
|
268
|
+
f"{pat_count} pattern(s)/{typ_count} type(s)/{atk_count} attack(s), "
|
|
269
|
+
f"have {len(raw16)}")
|
|
270
|
+
off = _HDR
|
|
271
|
+
patterns = []
|
|
272
|
+
for _ in range(pat_count):
|
|
273
|
+
t = struct.unpack_from(_PAT_FMT, raw16, off)
|
|
274
|
+
rate, mc, cam, p0, ap = t[0], t[1], t[2], t[3], t[4]
|
|
275
|
+
puts = []
|
|
276
|
+
for j in range(4):
|
|
277
|
+
b = 5 + j * 8 # 5 scalars before the 4 puts in the tuple
|
|
278
|
+
puts.append(Put(t[b], t[b + 1], t[b + 2], t[b + 3], t[b + 4], t[b + 5], t[b + 6], t[b + 7]))
|
|
279
|
+
patterns.append(Pattern(rate, mc, cam, p0, ap, puts))
|
|
280
|
+
off += _PAT
|
|
281
|
+
monsters = []
|
|
282
|
+
for _ in range(typ_count):
|
|
283
|
+
monsters.append(MonParm.unpack(raw16, off))
|
|
284
|
+
off += _MON
|
|
285
|
+
attacks = []
|
|
286
|
+
for _ in range(atk_count):
|
|
287
|
+
attacks.append(Attack(*struct.unpack_from(_ATK_FMT, raw16, off)))
|
|
288
|
+
off += _ATK
|
|
289
|
+
tail = bytes(raw16[off:])
|
|
290
|
+
return Scene(head=head, patterns=patterns, monsters=monsters, attacks=attacks, tail=tail)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def serialize_scene(scene: Scene) -> bytes:
|
|
294
|
+
"""Re-emit a :class:`Scene` to raw16 bytes. ``serialize_scene(parse_scene(x)) == x`` for any valid x."""
|
|
295
|
+
out = bytearray(scene.head)
|
|
296
|
+
for p in scene.patterns:
|
|
297
|
+
puts = []
|
|
298
|
+
for q in p.puts:
|
|
299
|
+
puts += [q.type_no, q.flags, q.pease, q.pad, q.x, q.y, q.z, q.rot]
|
|
300
|
+
out += struct.pack(_PAT_FMT, p.rate, p.monster_count, p.camera, p.pad0, p.ap, *puts)
|
|
301
|
+
for m in scene.monsters:
|
|
302
|
+
out += m.pack()
|
|
303
|
+
for a in scene.attacks:
|
|
304
|
+
out += struct.pack(_ATK_FMT, a.info, a.script_id, a.power, a.elements, a.rate,
|
|
305
|
+
a.category, a.add_status, a.mp, a.type, a.vfx2, a.name)
|
|
306
|
+
out += scene.tail
|
|
307
|
+
return bytes(out)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def scene_counts(raw16: bytes):
|
|
311
|
+
"""(pat_count, typ_count, atk_count) -- a cheap header peek without a full parse."""
|
|
312
|
+
if len(raw16) < _HDR:
|
|
313
|
+
raise SceneCodecError("raw16 too short")
|
|
314
|
+
return raw16[1], raw16[2], raw16[3]
|