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.
Files changed (193) hide show
  1. ff9mapkit/__init__.py +18 -0
  2. ff9mapkit/__main__.py +36 -0
  3. ff9mapkit/_animdb.py +2994 -0
  4. ff9mapkit/_animdb_all.py +14125 -0
  5. ff9mapkit/_fieldtable.py +1516 -0
  6. ff9mapkit/_fieldtext.py +845 -0
  7. ff9mapkit/_held_poses.py +44 -0
  8. ff9mapkit/_itemdb.py +65 -0
  9. ff9mapkit/_modeldb.py +725 -0
  10. ff9mapkit/_narrowmap_data.py +10 -0
  11. ff9mapkit/_npcparams.py +634 -0
  12. ff9mapkit/_regen_animdb.py +72 -0
  13. ff9mapkit/_regen_animdb_all.py +66 -0
  14. ff9mapkit/_regen_fieldtable.py +95 -0
  15. ff9mapkit/_regen_fieldtext.py +66 -0
  16. ff9mapkit/_regen_modeldb.py +67 -0
  17. ff9mapkit/_regen_npcparams.py +123 -0
  18. ff9mapkit/_regen_scenedb.py +57 -0
  19. ff9mapkit/_scenedb.py +869 -0
  20. ff9mapkit/abilities.py +225 -0
  21. ff9mapkit/animations.py +120 -0
  22. ff9mapkit/archetypes.py +218 -0
  23. ff9mapkit/areatitle.py +76 -0
  24. ff9mapkit/battle/__init__.py +21 -0
  25. ff9mapkit/battle/abilityfeatures.py +294 -0
  26. ff9mapkit/battle/actiondelta.py +441 -0
  27. ff9mapkit/battle/aiauthor.py +305 -0
  28. ff9mapkit/battle/ailint.py +140 -0
  29. ff9mapkit/battle/aipatch.py +175 -0
  30. ff9mapkit/battle/battleai.py +148 -0
  31. ff9mapkit/battle/battlecsv.py +390 -0
  32. ff9mapkit/battle/battlepatch.py +395 -0
  33. ff9mapkit/battle/build.py +558 -0
  34. ff9mapkit/battle/camera_codec.py +332 -0
  35. ff9mapkit/battle/camera_data.py +128 -0
  36. ff9mapkit/battle/characterdelta.py +789 -0
  37. ff9mapkit/battle/event_data.py +72 -0
  38. ff9mapkit/battle/extract.py +540 -0
  39. ff9mapkit/battle/fbx.py +223 -0
  40. ff9mapkit/battle/reskin.py +149 -0
  41. ff9mapkit/battle/scene_codec.py +314 -0
  42. ff9mapkit/battle/scene_data.py +369 -0
  43. ff9mapkit/battle/scenelint.py +125 -0
  44. ff9mapkit/battle/seqasm.py +131 -0
  45. ff9mapkit/battle/seqauthor.py +220 -0
  46. ff9mapkit/battle/seqcodec.py +300 -0
  47. ff9mapkit/battle/seqdis.py +106 -0
  48. ff9mapkit/battle/seqpatch.py +137 -0
  49. ff9mapkit/battle_bgm.py +133 -0
  50. ff9mapkit/binutils.py +60 -0
  51. ff9mapkit/build.py +5445 -0
  52. ff9mapkit/campaign.py +1276 -0
  53. ff9mapkit/catalog.py +316 -0
  54. ff9mapkit/chain.py +358 -0
  55. ff9mapkit/cli.py +3114 -0
  56. ff9mapkit/config.py +360 -0
  57. ff9mapkit/content/__init__.py +13 -0
  58. ff9mapkit/content/areatitle.py +36 -0
  59. ff9mapkit/content/ate.py +118 -0
  60. ff9mapkit/content/camera.py +123 -0
  61. ff9mapkit/content/chest.py +186 -0
  62. ff9mapkit/content/choice.py +163 -0
  63. ff9mapkit/content/conductor.py +217 -0
  64. ff9mapkit/content/cutscene.py +290 -0
  65. ff9mapkit/content/encounter.py +41 -0
  66. ff9mapkit/content/entry_settle.py +50 -0
  67. ff9mapkit/content/equipment.py +93 -0
  68. ff9mapkit/content/event.py +191 -0
  69. ff9mapkit/content/gateway.py +101 -0
  70. ff9mapkit/content/inventory.py +59 -0
  71. ff9mapkit/content/itemdata.py +644 -0
  72. ff9mapkit/content/itemtext.py +168 -0
  73. ff9mapkit/content/jump.py +114 -0
  74. ff9mapkit/content/ladder.py +633 -0
  75. ff9mapkit/content/movement.py +53 -0
  76. ff9mapkit/content/music.py +97 -0
  77. ff9mapkit/content/npc.py +348 -0
  78. ff9mapkit/content/object.py +340 -0
  79. ff9mapkit/content/onentry.py +135 -0
  80. ff9mapkit/content/party.py +111 -0
  81. ff9mapkit/content/pathfind.py +138 -0
  82. ff9mapkit/content/platform.py +314 -0
  83. ff9mapkit/content/player.py +168 -0
  84. ff9mapkit/content/prop.py +75 -0
  85. ff9mapkit/content/region.py +340 -0
  86. ff9mapkit/content/reinit.py +59 -0
  87. ff9mapkit/content/savepoint.py +90 -0
  88. ff9mapkit/content/shop.py +178 -0
  89. ff9mapkit/content/sps_trigger.py +66 -0
  90. ff9mapkit/content/startup.py +71 -0
  91. ff9mapkit/content/synthesis.py +106 -0
  92. ff9mapkit/content/text.py +183 -0
  93. ff9mapkit/content/textcarry.py +290 -0
  94. ff9mapkit/content/verbatim.py +86 -0
  95. ff9mapkit/content/walkmesh_hotfix.py +38 -0
  96. ff9mapkit/data/__init__.py +48 -0
  97. ff9mapkit/data/_regen_provenance.py +142 -0
  98. ff9mapkit/data/provenance/blank.es.patch +1 -0
  99. ff9mapkit/data/provenance/blank.fr.patch +1 -0
  100. ff9mapkit/data/provenance/blank.gr.patch +1 -0
  101. ff9mapkit/data/provenance/blank.it.patch +1 -0
  102. ff9mapkit/data/provenance/blank.jp.patch +1 -0
  103. ff9mapkit/data/provenance/blank.uk.patch +1 -0
  104. ff9mapkit/data/provenance/blank.us.patch +1 -0
  105. ff9mapkit/data/provenance/manifest.json +65 -0
  106. ff9mapkit/data/provenance/region_template.patch +1 -0
  107. ff9mapkit/data/reference_arcs.toml +89 -0
  108. ff9mapkit/data/region_catalog.toml +593 -0
  109. ff9mapkit/deploystack.py +358 -0
  110. ff9mapkit/dialogue.py +803 -0
  111. ff9mapkit/eb/__init__.py +12 -0
  112. ff9mapkit/eb/_exprtable.py +59 -0
  113. ff9mapkit/eb/_membertable.py +38 -0
  114. ff9mapkit/eb/_optables.py +537 -0
  115. ff9mapkit/eb/_regen_optables.py +76 -0
  116. ff9mapkit/eb/cmdasm.py +323 -0
  117. ff9mapkit/eb/disasm.py +332 -0
  118. ff9mapkit/eb/edit.py +439 -0
  119. ff9mapkit/eb/exprasm.py +158 -0
  120. ff9mapkit/eb/model.py +178 -0
  121. ff9mapkit/eb/opcodes.py +463 -0
  122. ff9mapkit/eblint.py +177 -0
  123. ff9mapkit/editor/__init__.py +20 -0
  124. ff9mapkit/editor/app.py +950 -0
  125. ff9mapkit/editor/battle_forms.py +240 -0
  126. ff9mapkit/editor/breadcrumb.py +89 -0
  127. ff9mapkit/editor/dialogs.py +116 -0
  128. ff9mapkit/editor/feedback.py +208 -0
  129. ff9mapkit/editor/forms.py +632 -0
  130. ff9mapkit/editor/graphview.py +350 -0
  131. ff9mapkit/editor/jobs.py +342 -0
  132. ff9mapkit/editor/model.py +243 -0
  133. ff9mapkit/editor/picker.py +120 -0
  134. ff9mapkit/editor/theme.py +212 -0
  135. ff9mapkit/eventscan.py +1441 -0
  136. ff9mapkit/extract.py +2279 -0
  137. ff9mapkit/flags.py +693 -0
  138. ff9mapkit/forkreport.py +1383 -0
  139. ff9mapkit/hub.py +477 -0
  140. ff9mapkit/idgated.py +101 -0
  141. ff9mapkit/infohub.py +580 -0
  142. ff9mapkit/items.py +63 -0
  143. ff9mapkit/itemstats.py +346 -0
  144. ff9mapkit/journey.py +1902 -0
  145. ff9mapkit/keyitems.py +93 -0
  146. ff9mapkit/logic_add.py +632 -0
  147. ff9mapkit/logic_edit.py +728 -0
  148. ff9mapkit/logic_map.py +526 -0
  149. ff9mapkit/pack.py +175 -0
  150. ff9mapkit/playerswap.py +231 -0
  151. ff9mapkit/prop_archetypes.py +228 -0
  152. ff9mapkit/provision.py +282 -0
  153. ff9mapkit/refarc.py +825 -0
  154. ff9mapkit/save.py +337 -0
  155. ff9mapkit/save_items.py +1673 -0
  156. ff9mapkit/scene/__init__.py +11 -0
  157. ff9mapkit/scene/arena.py +63 -0
  158. ff9mapkit/scene/bgart.py +140 -0
  159. ff9mapkit/scene/bgi.py +732 -0
  160. ff9mapkit/scene/bgs.py +174 -0
  161. ff9mapkit/scene/bgx.py +185 -0
  162. ff9mapkit/scene/cam.py +345 -0
  163. ff9mapkit/scene/guide.py +311 -0
  164. ff9mapkit/scene/paint.py +506 -0
  165. ff9mapkit/scene/placeholder.py +107 -0
  166. ff9mapkit/sjbinary.py +285 -0
  167. ff9mapkit/sps/__init__.py +17 -0
  168. ff9mapkit/sps/author.py +294 -0
  169. ff9mapkit/sps/catalog.py +88 -0
  170. ff9mapkit/sps/codec.py +264 -0
  171. ff9mapkit/sps/edit.py +184 -0
  172. ff9mapkit/sps/lint.py +58 -0
  173. ff9mapkit/sps/render.py +116 -0
  174. ff9mapkit/sps/templates.py +47 -0
  175. ff9mapkit/sps/texture.py +131 -0
  176. ff9mapkit/walkmesh_hotfixes.py +163 -0
  177. ff9mapkit/workspace/__init__.py +18 -0
  178. ff9mapkit/workspace/battledoc.py +985 -0
  179. ff9mapkit/workspace/builddoc.py +607 -0
  180. ff9mapkit/workspace/forms_qt.py +586 -0
  181. ff9mapkit/workspace/importdoc.py +665 -0
  182. ff9mapkit/workspace/mapview.py +131 -0
  183. ff9mapkit/workspace/palette.py +85 -0
  184. ff9mapkit/workspace/savedoc.py +664 -0
  185. ff9mapkit/workspace/shell.py +6907 -0
  186. ff9mapkit/workspace/style.py +105 -0
  187. ff9mapkit/workspace/tuningdialog.py +223 -0
  188. ff9mapkit-1.0.0b3.dist-info/METADATA +155 -0
  189. ff9mapkit-1.0.0b3.dist-info/RECORD +193 -0
  190. ff9mapkit-1.0.0b3.dist-info/WHEEL +5 -0
  191. ff9mapkit-1.0.0b3.dist-info/entry_points.txt +5 -0
  192. ff9mapkit-1.0.0b3.dist-info/licenses/LICENSE +31 -0
  193. ff9mapkit-1.0.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ """A QGraphicsView campaign MAP for the Workspace -- the visual twin of the project tree.
2
+
3
+ The placement is the SAME tk-free core the tkinter Campaign Editor uses: ``editor.graphview.compute_layout``
4
+ lays a :class:`..campaign.CampaignGraph` out top-down in BFS levels (entry at the top, unreachable members
5
+ below) and returns absolute node/edge/seam coordinates. This module only RENDERS that ``GraphLayout`` into
6
+ a Qt scene (rounded-rect nodes coloured by health, arrowed gateway edges -- dashed when gated -- and dashed
7
+ seam stubs) and turns a double-click into an open-member call. So both editors draw the identical map.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import math
13
+
14
+ from PySide6.QtCore import QPointF, QRectF, Qt
15
+ from PySide6.QtGui import QBrush, QColor, QFont, QPainter, QPainterPath, QPen, QPolygonF
16
+ from PySide6.QtWidgets import QGraphicsScene, QGraphicsView
17
+
18
+ from ..editor.graphview import compute_layout
19
+
20
+
21
+ class CampaignMap(QGraphicsView):
22
+ """A scrollable, pannable node-link map of a campaign. ``on_open(name)`` fires on double-clicking a
23
+ node; the open member is highlighted (accent fill). Call :meth:`render` with a CampaignGraph."""
24
+
25
+ def __init__(self, palette, *, on_open=None):
26
+ super().__init__()
27
+ self.pal = palette
28
+ self.on_open = on_open
29
+ self._layout = None
30
+ self._current = None
31
+ self._scene = QGraphicsScene(self)
32
+ self.setScene(self._scene)
33
+ self.setRenderHint(QPainter.RenderHint.Antialiasing)
34
+ self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # click-drag to pan
35
+ self.setBackgroundBrush(QColor(palette["surface"]))
36
+
37
+ # -- public --
38
+ def render(self, graph, current=None):
39
+ self._layout = compute_layout(graph)
40
+ self._current = current
41
+ self._draw()
42
+
43
+ def highlight(self, name):
44
+ if self._layout is None:
45
+ return
46
+ self._current = name
47
+ self._draw()
48
+
49
+ def clear(self):
50
+ self._scene.clear()
51
+ self._layout = None
52
+ self._current = None
53
+
54
+ # -- drawing --
55
+ def _draw(self):
56
+ sc, pal, lay = self._scene, self.pal, self._layout
57
+ sc.clear()
58
+ if lay is None:
59
+ return
60
+ sc.setSceneRect(0, 0, lay.width, lay.height)
61
+ muted = QColor(pal["muted"])
62
+ for e in lay.edges: # edges under the nodes
63
+ pen = QPen(muted, 2)
64
+ if e.gated:
65
+ pen.setDashPattern([5, 3])
66
+ sc.addLine(e.x1, e.y1, e.x2, e.y2, pen)
67
+ self._arrow_head(e.x1, e.y1, e.x2, e.y2, muted)
68
+ for s in lay.seams:
69
+ pen = QPen(muted, 1)
70
+ pen.setDashPattern([2, 3])
71
+ sc.addLine(s.nx, s.ny, s.x, s.y, pen)
72
+ t = sc.addSimpleText("~ " + s.label, QFont("Segoe UI", 8))
73
+ t.setBrush(QBrush(muted))
74
+ t.setPos(s.x + 4, s.y - 7)
75
+ for n in lay.nodes:
76
+ self._node(n)
77
+
78
+ def _node(self, n):
79
+ sc, pal = self._scene, self.pal
80
+ if not n.reachable:
81
+ outline = pal["error"]
82
+ elif n.needs_export:
83
+ outline = pal["warn"]
84
+ elif n.is_entry:
85
+ outline = pal["success"]
86
+ else:
87
+ outline = pal["border"]
88
+ current = (n.name == self._current)
89
+ fill = pal["accent"] if current else pal["surface_btn"]
90
+ tcol = pal["accent_fg"] if current else pal["text"]
91
+ sub_col = pal["accent_fg"] if current else pal["muted"]
92
+ path = QPainterPath()
93
+ path.addRoundedRect(QRectF(n.x, n.y, n.w, n.h), 10, 10)
94
+ sc.addPath(path, QPen(QColor(outline), 2 if outline != pal["border"] else 1), QBrush(QColor(fill)))
95
+ title = sc.addSimpleText(n.name, QFont("Segoe UI", 10, QFont.Weight.Bold))
96
+ title.setBrush(QBrush(QColor(tcol)))
97
+ title.setPos(n.cx - title.boundingRect().width() / 2, n.y + 8)
98
+ sub = sc.addSimpleText(f"id {n.new_id}" + ("" if n.mode == "borrow" else f" · {n.mode}"),
99
+ QFont("Segoe UI", 8))
100
+ sub.setBrush(QBrush(QColor(sub_col)))
101
+ sub.setPos(n.cx - sub.boundingRect().width() / 2, n.y + 26)
102
+
103
+ def _arrow_head(self, x1, y1, x2, y2, color, size=11):
104
+ dx, dy = x2 - x1, y2 - y1
105
+ d = math.hypot(dx, dy)
106
+ if d == 0:
107
+ return
108
+ ux, uy = dx / d, dy / d
109
+ bx, by = x2 - ux * size, y2 - uy * size # base of the head, back along the line
110
+ px, py = -uy, ux # perpendicular
111
+ head = QPolygonF([QPointF(x2, y2),
112
+ QPointF(bx + px * size * 0.5, by + py * size * 0.5),
113
+ QPointF(bx - px * size * 0.5, by - py * size * 0.5)])
114
+ self._scene.addPolygon(head, QPen(color, 0), QBrush(color))
115
+
116
+ # -- interaction --
117
+ def _node_at(self, view_pos):
118
+ if self._layout is None:
119
+ return None
120
+ sp = self.mapToScene(view_pos)
121
+ for n in self._layout.nodes:
122
+ if n.x <= sp.x() <= n.x + n.w and n.y <= sp.y() <= n.y + n.h:
123
+ return n.name
124
+ return None
125
+
126
+ def mouseDoubleClickEvent(self, event): # noqa: N802 (Qt override)
127
+ name = self._node_at(event.position().toPoint())
128
+ if name and self.on_open:
129
+ self.on_open(name)
130
+ else:
131
+ super().mouseDoubleClickEvent(event)
@@ -0,0 +1,85 @@
1
+ """The Ctrl-K command palette for the Workspace -- fuzzy search over content + named commands.
2
+
3
+ A modal overlay: type to filter a flat list of entries (each ``(label, kind, callback)``), Enter / click
4
+ runs the selected one. The shell feeds it the named commands (Open Campaign, Check, switch tab, …) AND
5
+ the project content (every journey / campaign / field / object node in the tree), so a clicker and a
6
+ keyboard user reach the same place. Matching is a subsequence test (``ocm`` -> "Open Campaign"), so
7
+ abbreviations work. PySide6-only view; the entry list + callbacks are the shell's to build.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from PySide6.QtCore import QEvent, Qt
13
+ from PySide6.QtWidgets import QDialog, QLineEdit, QListWidget, QListWidgetItem, QVBoxLayout
14
+
15
+
16
+ def fuzzy(needle: str, hay: str) -> bool:
17
+ """True if every char of ``needle`` appears in ``hay`` IN ORDER (a subsequence match). Both lower."""
18
+ i = 0
19
+ for ch in hay:
20
+ if i < len(needle) and ch == needle[i]:
21
+ i += 1
22
+ return i == len(needle)
23
+
24
+
25
+ def rank(needle: str, label: str, kind: str):
26
+ """A sort key: exact substring first, then earliest match, then shorter labels. Lower = better."""
27
+ lab = label.lower()
28
+ sub = lab.find(needle)
29
+ return (0 if sub == 0 else 1 if sub > 0 else 2, sub if sub >= 0 else 1_000, len(label))
30
+
31
+
32
+ class CommandPalette(QDialog):
33
+ """Type-to-filter overlay over ``entries`` (a list of ``(label, kind, callback)``)."""
34
+
35
+ def __init__(self, parent, entries, palette):
36
+ super().__init__(parent)
37
+ self.setWindowTitle("Search content & commands")
38
+ self.setModal(True)
39
+ self.resize(580, 440)
40
+ self._entries = list(entries)
41
+ self._filtered = list(entries)
42
+ lay = QVBoxLayout(self)
43
+ self.q = QLineEdit()
44
+ self.q.setPlaceholderText("Search content & commands… (a field name, or a command)")
45
+ self.q.textChanged.connect(self._refilter)
46
+ self.q.returnPressed.connect(self._run_current)
47
+ self.q.installEventFilter(self) # forward Up/Down to the list while typing
48
+ lay.addWidget(self.q)
49
+ self.lst = QListWidget()
50
+ self.lst.itemActivated.connect(lambda _i: self._run_current())
51
+ lay.addWidget(self.lst, 1)
52
+ self._muted = palette["muted"]
53
+ self._fill()
54
+ self.q.setFocus()
55
+
56
+ def _fill(self):
57
+ self.lst.clear()
58
+ for label, kind, _cb in self._filtered:
59
+ it = QListWidgetItem(f"{label} · {kind}")
60
+ self.lst.addItem(it)
61
+ if self._filtered:
62
+ self.lst.setCurrentRow(0)
63
+
64
+ def _refilter(self, text):
65
+ t = text.strip().lower()
66
+ if not t:
67
+ self._filtered = list(self._entries)
68
+ else:
69
+ self._filtered = sorted(
70
+ (e for e in self._entries if fuzzy(t, e[0].lower()) or fuzzy(t, (e[0] + " " + e[1]).lower())),
71
+ key=lambda e: rank(t, e[0], e[1]))
72
+ self._fill()
73
+
74
+ def _run_current(self):
75
+ r = self.lst.currentRow()
76
+ if 0 <= r < len(self._filtered):
77
+ cb = self._filtered[r][2]
78
+ self.accept()
79
+ cb()
80
+
81
+ def eventFilter(self, obj, ev): # noqa: N802 (Qt override)
82
+ if obj is self.q and ev.type() == QEvent.Type.KeyPress and ev.key() in (Qt.Key_Down, Qt.Key_Up):
83
+ self.lst.keyPressEvent(ev) # arrow keys move the list while focus stays in the box
84
+ return True
85
+ return super().eventFilter(obj, ev)