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,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)
|