tfbscript 0.1.0__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.
- tfbscript/__init__.py +21 -0
- tfbscript/__main__.py +52 -0
- tfbscript/ansi.py +203 -0
- tfbscript/arena.py +119 -0
- tfbscript/binary.py +84 -0
- tfbscript/debug.py +14 -0
- tfbscript/editor/__init__.py +3 -0
- tfbscript/editor/app.py +141 -0
- tfbscript/editor/fonts/ms-sans-serif/MS Sans Serif.ttf +0 -0
- tfbscript/editor/fonts/ms-sans-serif/license.txt +4 -0
- tfbscript/editor/fonts/ms-sans-serif/readme.txt +26 -0
- tfbscript/editor/fonts/ms-sans-serif-bold/MS Sans Serif Bold.ttf +0 -0
- tfbscript/editor/fonts/ms-sans-serif-bold/license.txt +4 -0
- tfbscript/editor/fonts/ms-sans-serif-bold/readme.txt +26 -0
- tfbscript/editor/fonts.py +98 -0
- tfbscript/editor/search.py +200 -0
- tfbscript/editor/style.py +141 -0
- tfbscript/editor/widgets.py +321 -0
- tfbscript/opcodes/__init__.py +104 -0
- tfbscript/opcodes/base.py +362 -0
- tfbscript/opcodes/block.py +34 -0
- tfbscript/opcodes/enums.py +219 -0
- tfbscript/opcodes/op_behavior_implementation.py +39 -0
- tfbscript/opcodes/op_change_membership.py +62 -0
- tfbscript/opcodes/op_check_fov.py +73 -0
- tfbscript/opcodes/op_check_membership.py +41 -0
- tfbscript/opcodes/op_check_message.py +79 -0
- tfbscript/opcodes/op_check_reference.py +61 -0
- tfbscript/opcodes/op_check_value.py +66 -0
- tfbscript/opcodes/op_comment.py +43 -0
- tfbscript/opcodes/op_control.py +80 -0
- tfbscript/opcodes/op_create_variable.py +42 -0
- tfbscript/opcodes/op_cut_scene.py +44 -0
- tfbscript/opcodes/op_dec_value.py +43 -0
- tfbscript/opcodes/op_displace.py +111 -0
- tfbscript/opcodes/op_find_subset.py +49 -0
- tfbscript/opcodes/op_find_variable.py +28 -0
- tfbscript/opcodes/op_for_each.py +65 -0
- tfbscript/opcodes/op_if_else.py +105 -0
- tfbscript/opcodes/op_inc_value.py +43 -0
- tfbscript/opcodes/op_loop_value.py +28 -0
- tfbscript/opcodes/op_move_from.py +47 -0
- tfbscript/opcodes/op_move_to.py +99 -0
- tfbscript/opcodes/op_play_animation.py +46 -0
- tfbscript/opcodes/op_play_sound.py +42 -0
- tfbscript/opcodes/op_prescript.py +26 -0
- tfbscript/opcodes/op_print.py +53 -0
- tfbscript/opcodes/op_remove.py +43 -0
- tfbscript/opcodes/op_reset.py +42 -0
- tfbscript/opcodes/op_run_as_player.py +26 -0
- tfbscript/opcodes/op_send_message.py +53 -0
- tfbscript/opcodes/op_set_behavior.py +41 -0
- tfbscript/opcodes/op_set_reference.py +52 -0
- tfbscript/opcodes/op_set_value.py +52 -0
- tfbscript/opcodes/op_shutdown.py +26 -0
- tfbscript/opcodes/op_slide_value.py +81 -0
- tfbscript/opcodes/op_spawn_actor.py +96 -0
- tfbscript/opcodes/op_startup.py +26 -0
- tfbscript/opcodes/op_stop_sound.py +26 -0
- tfbscript/opcodes/op_teleport_to.py +47 -0
- tfbscript/opcodes/op_turn_to.py +33 -0
- tfbscript/opcodes/op_use_camera.py +68 -0
- tfbscript/payload.py +40 -0
- tfbscript/reference.py +926 -0
- tfbscript/rhs.py +326 -0
- tfbscript/script.py +160 -0
- tfbscript/string_table.py +101 -0
- tfbscript-0.1.0.dist-info/METADATA +116 -0
- tfbscript-0.1.0.dist-info/RECORD +72 -0
- tfbscript-0.1.0.dist-info/WHEEL +4 -0
- tfbscript-0.1.0.dist-info/entry_points.txt +2 -0
- tfbscript-0.1.0.dist-info/licenses/LICENSE +201 -0
tfbscript/reference.py
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, BinaryIO, TypeVar, override
|
|
5
|
+
|
|
6
|
+
from tfbscript.ansi import builtin, keyword, variable, variable_global
|
|
7
|
+
from tfbscript.binary import write_u32
|
|
8
|
+
from tfbscript.string_table import StringTable, StringTableEntry
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from tfbscript.binary import BinaryReader
|
|
12
|
+
from tfbscript.opcodes.base import Opcode, ParserContext
|
|
13
|
+
|
|
14
|
+
_OpcodeT = TypeVar("_OpcodeT", bound="Opcode")
|
|
15
|
+
|
|
16
|
+
LOCAL_BASE = 0x3FC0D
|
|
17
|
+
BUILTIN_BASE = 0x3FFFA
|
|
18
|
+
NULL_REF = 0xFFFFFFFF
|
|
19
|
+
|
|
20
|
+
# References whose type resolution is in progress, to break cyclic
|
|
21
|
+
# builtin producer chains (see get_resolved_type).
|
|
22
|
+
_RESOLVING: set[int] = set()
|
|
23
|
+
|
|
24
|
+
# Fields `raw` is composed from: assigning any of them re-runs update_raw.
|
|
25
|
+
_RAW_INPUTS = frozenset(
|
|
26
|
+
{"index", "member", "scope", "sub", "kind", "builtin_kind", "slot"}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# fmt: off
|
|
31
|
+
class ReferenceType(Enum):
|
|
32
|
+
LOCAL = 0
|
|
33
|
+
GLOBAL = 1
|
|
34
|
+
BUILTIN = 2
|
|
35
|
+
NULL = 3
|
|
36
|
+
|
|
37
|
+
class BuiltinType(Enum):
|
|
38
|
+
SELF = 0x3FFFF
|
|
39
|
+
CONTROLLED = 0x3FFFE
|
|
40
|
+
EACH = 0x3FFFD # current in for each
|
|
41
|
+
SUBSET = 0x3FFFC # filtered list of any kind
|
|
42
|
+
MESSAGE_VALUE = 0x3FFFB
|
|
43
|
+
FOUND_VARIABLE = 0x3FFFA
|
|
44
|
+
|
|
45
|
+
BUILTIN_LABELS = { # type resolving implemented
|
|
46
|
+
BuiltinType.SELF : "my", # yes
|
|
47
|
+
BuiltinType.CONTROLLED : "[~controlled]", # yes
|
|
48
|
+
BuiltinType.EACH : "[~current in for each]", # no (see comment)
|
|
49
|
+
BuiltinType.SUBSET : "[~subset]", # yes
|
|
50
|
+
BuiltinType.MESSAGE_VALUE : "[~message value]", # yes
|
|
51
|
+
BuiltinType.FOUND_VARIABLE: "[~found variable]", # yes
|
|
52
|
+
}
|
|
53
|
+
# fmt: on
|
|
54
|
+
|
|
55
|
+
BINDINGS = {
|
|
56
|
+
"actor": {
|
|
57
|
+
0x01: {"name": "health", "type": "value"},
|
|
58
|
+
0x02: {"name": "waypoints", "type": "set", "of": "waypoint"},
|
|
59
|
+
0x03: {"name": "origin", "type": "value"},
|
|
60
|
+
0x04: {"name": "destination (point)", "type": "value"},
|
|
61
|
+
0x05: {"name": "heading (OBSOLETE)", "type": "value"},
|
|
62
|
+
0x06: {"name": "facing (OBSOLETE)", "type": "value"},
|
|
63
|
+
0x07: {"name": "current speed", "type": "value"},
|
|
64
|
+
0x08: {"name": "altitude", "type": "value"},
|
|
65
|
+
0x09: {"name": "attach points", "type": "set", "of": "attachment"},
|
|
66
|
+
0x0A: {"name": "activation range fade percent", "type": "value"},
|
|
67
|
+
0x0B: {"name": "activation range", "type": "value"},
|
|
68
|
+
0x0C: {"name": "deactivation range", "type": "value"},
|
|
69
|
+
0x0D: {"name": "clones", "type": "set", "of": "actor"},
|
|
70
|
+
0x0E: {"name": "ground top speed", "type": "value"},
|
|
71
|
+
0x0F: {"name": "ground turn rate", "type": "value"},
|
|
72
|
+
0x10: {"name": "ground acceleration", "type": "value"},
|
|
73
|
+
0x11: {"name": "ground deceleration", "type": "value"},
|
|
74
|
+
0x12: {"name": "move type", "type": "value"},
|
|
75
|
+
0x13: {"name": "air top speed", "type": "value"},
|
|
76
|
+
0x14: {"name": "air turn rate", "type": "value"},
|
|
77
|
+
0x15: {"name": "air acceleration", "type": "value"},
|
|
78
|
+
0x16: {"name": "air deceleration", "type": "value"},
|
|
79
|
+
0x17: {"name": "jump delay", "type": "value"},
|
|
80
|
+
0x18: {"name": "initial jump velocity", "type": "value"},
|
|
81
|
+
0x19: {"name": "jump coast frames", "type": "value"},
|
|
82
|
+
0x1A: {"name": "up top speed", "type": "value"},
|
|
83
|
+
0x1B: {"name": "up acceleration", "type": "value"},
|
|
84
|
+
0x1C: {"name": "down top speed", "type": "value"},
|
|
85
|
+
0x1D: {"name": "down acceleration", "type": "value"},
|
|
86
|
+
0x1E: {"name": "animation rate multiplier", "type": "value"},
|
|
87
|
+
0x1F: {"name": "weight", "type": "value"},
|
|
88
|
+
0x20: {"name": "bounce restitution", "type": "value"},
|
|
89
|
+
0x21: {"name": "X offset", "type": "value"}, # Extend box X
|
|
90
|
+
0x22: {"name": "Y offset", "type": "value"}, # Extend box Y
|
|
91
|
+
0x23: {"name": "Z offset", "type": "value"}, # Extend box Z
|
|
92
|
+
0x24: {"name": "W extend", "type": "value"}, # Extend box W extend
|
|
93
|
+
0x25: {"name": "L extend", "type": "value"}, # Extend box L extend
|
|
94
|
+
0x26: {"name": "H extend", "type": "value"}, # Extend box H extend
|
|
95
|
+
0x27: {"name": "cone angle", "type": "value"},
|
|
96
|
+
0x28: {"name": "cone length", "type": "value"},
|
|
97
|
+
0x29: {"name": "cone sweep offset", "type": "value"},
|
|
98
|
+
0x2A: {"name": "blip color", "type": "value", "of": "color"},
|
|
99
|
+
0x2B: {"name": "cone color", "type": "value", "of": "color"},
|
|
100
|
+
0x2C: {"name": "solid collision", "type": "value"},
|
|
101
|
+
0x2D: {"name": "wall collision", "type": "value"},
|
|
102
|
+
0x2E: {"name": "visible on radar", "type": "value"},
|
|
103
|
+
0x2F: {"name": "wall climber", "type": "value"},
|
|
104
|
+
0x30: {"name": "cut-scene ignore", "type": "value"},
|
|
105
|
+
0x31: {"name": "ignore ground color", "type": "value"},
|
|
106
|
+
0x32: {"name": "tilts with ground", "type": "value"},
|
|
107
|
+
0x33: {"name": "no shadow", "type": "value"},
|
|
108
|
+
0x34: {"name": "ID", "type": "value"},
|
|
109
|
+
0x35: {"name": "mesh collider", "type": "value"},
|
|
110
|
+
0x36: {"name": "triangles collide", "type": "value"},
|
|
111
|
+
0x37: {"name": "tint color", "type": "value", "of": "color"},
|
|
112
|
+
0x38: {"name": "turrets", "type": "set", "of": "turret"},
|
|
113
|
+
},
|
|
114
|
+
"camera": {
|
|
115
|
+
0x01: {"name": "look from", "type": "value"},
|
|
116
|
+
0x02: {"name": "look from horizontal offset", "type": "value"},
|
|
117
|
+
0x03: {"name": "look from vertical offset", "type": "value"},
|
|
118
|
+
0x04: {"name": "look from heading offset", "type": "value"},
|
|
119
|
+
0x05: {"name": "look from heading relative", "type": "value"},
|
|
120
|
+
0x06: {"name": "look at", "type": "value"},
|
|
121
|
+
0x07: {"name": "look at horizontal offset", "type": "value"},
|
|
122
|
+
0x08: {"name": "look at vertical offset", "type": "value"},
|
|
123
|
+
0x09: {"name": "look at heading offset", "type": "value"},
|
|
124
|
+
0x0A: {"name": "look at heading relative", "type": "value"},
|
|
125
|
+
0x0B: {"name": "field of view", "type": "value"},
|
|
126
|
+
0x0C: {"name": "laziness", "type": "value"},
|
|
127
|
+
0x0D: {"name": "percent complete", "type": "value"},
|
|
128
|
+
0x0E: {"name": "time remaining", "type": "value"},
|
|
129
|
+
0x0F: {"name": "Vertical Follow At", "type": "value"},
|
|
130
|
+
0x10: {"name": "Horizontal Follow", "type": "value"},
|
|
131
|
+
0x11: {"name": "Current Facing", "type": "value"},
|
|
132
|
+
0x12: {"name": "Vertical Follow From", "type": "value"},
|
|
133
|
+
0x13: {"name": "pitch control", "type": "value"},
|
|
134
|
+
0x14: {"name": "roll", "type": "value"},
|
|
135
|
+
},
|
|
136
|
+
"sprite": { # TODO: confirm labels
|
|
137
|
+
0x01: {"name": "content", "type": "value"},
|
|
138
|
+
0x02: {"name": "location", "type": "value", "of": "2D"},
|
|
139
|
+
0x03: {"name": "scale", "type": "value", "of": "2D"},
|
|
140
|
+
0x04: {"name": "rotation", "type": "value"},
|
|
141
|
+
0x05: {"name": "tint color", "type": "value", "of": "color"},
|
|
142
|
+
0x06: {"name": "justification", "type": "value"},
|
|
143
|
+
0x07: {"name": "visible", "type": "value"},
|
|
144
|
+
0x08: {"name": "clones", "type": "set", "of": "sprite"},
|
|
145
|
+
0x09: {"name": "priority", "type": "value"}, # z-order!?
|
|
146
|
+
0x0A: {"name": "pixel extends", "type": "value", "of": "2D"},
|
|
147
|
+
},
|
|
148
|
+
"sound": {
|
|
149
|
+
0x01: {"name": "volume", "type": "value"},
|
|
150
|
+
0x02: {"name": "pitch", "type": "value"},
|
|
151
|
+
0x03: {"name": "elapsed time", "type": "value"},
|
|
152
|
+
0x04: {"name": "remaining time", "type": "value"},
|
|
153
|
+
},
|
|
154
|
+
"2D": {
|
|
155
|
+
0x01: {"name": "x", "type": "value"},
|
|
156
|
+
0x02: {"name": "y", "type": "value"},
|
|
157
|
+
},
|
|
158
|
+
"color": {
|
|
159
|
+
0x01: {"name": "r", "type": "value"},
|
|
160
|
+
0x02: {"name": "g", "type": "value"},
|
|
161
|
+
0x03: {"name": "b", "type": "value"},
|
|
162
|
+
0x04: {"name": "a", "type": "value"},
|
|
163
|
+
},
|
|
164
|
+
"controller": {
|
|
165
|
+
0x01: {"name": "circle button", "type": "value"},
|
|
166
|
+
0x02: {"name": "triangle button", "type": "value"},
|
|
167
|
+
0x03: {"name": "X button", "type": "value"},
|
|
168
|
+
0x04: {"name": "square button", "type": "value"},
|
|
169
|
+
0x05: {"name": "select button", "type": "value"},
|
|
170
|
+
0x06: {"name": "start button", "type": "value"},
|
|
171
|
+
0x07: {"name": "L1 button", "type": "value"},
|
|
172
|
+
0x08: {"name": "R1 button", "type": "value"},
|
|
173
|
+
0x09: {"name": "L2 button", "type": "value"},
|
|
174
|
+
0x0A: {"name": "R2 button", "type": "value"},
|
|
175
|
+
0x0B: {"name": "DPad left", "type": "value"},
|
|
176
|
+
0x0C: {"name": "DPad right", "type": "value"},
|
|
177
|
+
0x0D: {"name": "DPad up", "type": "value"},
|
|
178
|
+
0x0E: {"name": "DPad down", "type": "value"},
|
|
179
|
+
0x0F: {"name": "analog1 left", "type": "value"},
|
|
180
|
+
0x10: {"name": "analog1 right", "type": "value"},
|
|
181
|
+
0x11: {"name": "analog1 up", "type": "value"},
|
|
182
|
+
0x12: {"name": "analog1 down", "type": "value"},
|
|
183
|
+
0x13: {"name": "analog2 left", "type": "value"},
|
|
184
|
+
0x14: {"name": "analog2 right", "type": "value"},
|
|
185
|
+
0x15: {"name": "analog2 up", "type": "value"},
|
|
186
|
+
0x16: {"name": "analog2 down", "type": "value"},
|
|
187
|
+
0x17: {"name": "analog1 heading", "type": "value"},
|
|
188
|
+
0x18: {"name": "analog1 magnitude", "type": "value"},
|
|
189
|
+
0x19: {"name": "analog2 heading", "type": "value"},
|
|
190
|
+
0x1A: {"name": "analog2 magnitude", "type": "value"},
|
|
191
|
+
0x1B: {"name": "connection", "type": "value"},
|
|
192
|
+
},
|
|
193
|
+
"MOTION BLUR": {
|
|
194
|
+
0x01: {"name": "amount", "type": "value"},
|
|
195
|
+
0x02: {"name": "offset-x", "type": "value"},
|
|
196
|
+
0x03: {"name": "offset-y", "type": "value"},
|
|
197
|
+
0x04: {"name": "scale x-axis", "type": "value"},
|
|
198
|
+
0x05: {"name": "scale y-axis", "type": "value"},
|
|
199
|
+
0x06: {"name": "scale", "type": "value"},
|
|
200
|
+
0x07: {"name": "angle", "type": "value"},
|
|
201
|
+
},
|
|
202
|
+
"animation mapping": {
|
|
203
|
+
0x01: {"name": "ambient", "type": "value"},
|
|
204
|
+
0x02: {"name": "slowest move", "type": "value"},
|
|
205
|
+
0x03: {"name": "slow move", "type": "value"},
|
|
206
|
+
0x04: {"name": "fast move", "type": "value"},
|
|
207
|
+
0x05: {"name": "jump", "type": "value"},
|
|
208
|
+
0x06: {"name": "fall", "type": "value"},
|
|
209
|
+
0x07: {"name": "youch", "type": "value"},
|
|
210
|
+
0x08: {"name": "slide", "type": "value"},
|
|
211
|
+
0x09: {"name": "attack 1", "type": "value"},
|
|
212
|
+
0x0A: {"name": "attack 2", "type": "value"},
|
|
213
|
+
0x0B: {"name": "tumble", "type": "value"},
|
|
214
|
+
0x0C: {"name": "extra 1", "type": "value"},
|
|
215
|
+
0x0D: {"name": "extra 2", "type": "value"},
|
|
216
|
+
0x0E: {"name": "extra 3", "type": "value"},
|
|
217
|
+
0x0F: {"name": "extra 4", "type": "value"},
|
|
218
|
+
0x10: {"name": "extra 5", "type": "value"},
|
|
219
|
+
0x11: {"name": "extra 6", "type": "value"},
|
|
220
|
+
0x12: {"name": "extra 7", "type": "value"},
|
|
221
|
+
0x13: {"name": "extra 8", "type": "value"},
|
|
222
|
+
0x14: {"name": "extra 9", "type": "value"},
|
|
223
|
+
0x15: {"name": "extra 10", "type": "value"},
|
|
224
|
+
0x16: {"name": "extra 11", "type": "value"},
|
|
225
|
+
0x17: {"name": "extra 12", "type": "value"},
|
|
226
|
+
0x18: {"name": "extra 13", "type": "value"},
|
|
227
|
+
0x19: {"name": "extra 14", "type": "value"},
|
|
228
|
+
0x1A: {"name": "extra 15", "type": "value"},
|
|
229
|
+
0x1B: {"name": "extra 16", "type": "value"},
|
|
230
|
+
0x1C: {"name": "extra 17", "type": "value"},
|
|
231
|
+
0x1D: {"name": "extra 18", "type": "value"},
|
|
232
|
+
0x1E: {"name": "extra 19", "type": "value"},
|
|
233
|
+
0x1F: {"name": "extra 20", "type": "value"},
|
|
234
|
+
0x20: {"name": "extra 21", "type": "value"},
|
|
235
|
+
0x21: {"name": "extra 22", "type": "value"},
|
|
236
|
+
0x22: {"name": "extra 23", "type": "value"},
|
|
237
|
+
0x23: {"name": "extra 24", "type": "value"},
|
|
238
|
+
0x24: {"name": "extra 25", "type": "value"},
|
|
239
|
+
0x25: {"name": "extra 26", "type": "value"},
|
|
240
|
+
0x26: {"name": "extra 27", "type": "value"},
|
|
241
|
+
0x27: {"name": "extra 28", "type": "value"},
|
|
242
|
+
0x28: {"name": "extra 29", "type": "value"},
|
|
243
|
+
0x29: {"name": "extra 30", "type": "value"},
|
|
244
|
+
0x2A: {"name": "extra 31", "type": "value"},
|
|
245
|
+
0x2B: {"name": "extra 32", "type": "value"},
|
|
246
|
+
0x2C: {"name": "left turn", "type": "value"},
|
|
247
|
+
0x2D: {"name": "right turn", "type": "value"},
|
|
248
|
+
0x2E: {"name": "jump land slow", "type": "value"},
|
|
249
|
+
0x2F: {"name": "jump land running", "type": "value"},
|
|
250
|
+
0x30: {"name": "jump land hard", "type": "value"},
|
|
251
|
+
0x31: {"name": "stop from full speed", "type": "value"},
|
|
252
|
+
0x32: {"name": "stop from slow speed", "type": "value"},
|
|
253
|
+
0x33: {"name": "running 180 change", "type": "value"},
|
|
254
|
+
0x34: {"name": "player 9", "type": "value"},
|
|
255
|
+
0x35: {"name": "player 10", "type": "value"},
|
|
256
|
+
0x36: {"name": "player 11", "type": "value"},
|
|
257
|
+
0x37: {"name": "player 12", "type": "value"},
|
|
258
|
+
0x38: {"name": "player 13", "type": "value"},
|
|
259
|
+
0x39: {"name": "player 14", "type": "value"},
|
|
260
|
+
0x3A: {"name": "lean left slow", "type": "value"},
|
|
261
|
+
0x3B: {"name": "lean left medium", "type": "value"},
|
|
262
|
+
0x3C: {"name": "lean left fast", "type": "value"},
|
|
263
|
+
0x3D: {"name": "lean right slow", "type": "value"},
|
|
264
|
+
0x3E: {"name": "lean right medium", "type": "value"},
|
|
265
|
+
0x3F: {"name": "lean right fast", "type": "value"},
|
|
266
|
+
},
|
|
267
|
+
"particle": {
|
|
268
|
+
0x01: {"name": "output rate", "type": "value"},
|
|
269
|
+
0x02: {"name": "output rate spread", "type": "value"},
|
|
270
|
+
0x03: {"name": "lifetime", "type": "value"},
|
|
271
|
+
0x04: {"name": "lifetime spread", "type": "value"},
|
|
272
|
+
0x05: {"name": "clones (list)", "type": "set", "of": "particle"},
|
|
273
|
+
0x06: {"name": "activation range", "type": "value"},
|
|
274
|
+
0x07: {"name": "deactivation range", "type": "value"},
|
|
275
|
+
0x08: {"name": "primary start velocity", "type": "value"},
|
|
276
|
+
0x09: {"name": "primary start velocity spread", "type": "value"},
|
|
277
|
+
0x0A: {"name": "primary start gravity", "type": "value"},
|
|
278
|
+
0x0B: {"name": "primary start drag", "type": "value"},
|
|
279
|
+
0x0C: {"name": "primary start color", "type": "value", "of": "color"},
|
|
280
|
+
0x0D: {"name": "primary start color spread", "type": "value", "of": "color"},
|
|
281
|
+
0x0E: {"name": "primary start size", "type": "value"},
|
|
282
|
+
0x0F: {"name": "primary start size spread", "type": "value"},
|
|
283
|
+
0x10: {"name": "primary end velocity", "type": "value"},
|
|
284
|
+
0x11: {"name": "primary end velocity spread", "type": "value"},
|
|
285
|
+
0x12: {"name": "primary end gravity", "type": "value"},
|
|
286
|
+
0x13: {"name": "primary end drag", "type": "value"},
|
|
287
|
+
0x14: {"name": "primary end color", "type": "value", "of": "color"},
|
|
288
|
+
0x15: {"name": "primary end color spread", "type": "value", "of": "color"},
|
|
289
|
+
0x16: {"name": "primary end size", "type": "value"},
|
|
290
|
+
0x17: {"name": "primary end size spread", "type": "value"},
|
|
291
|
+
0x18: {"name": "emitter spread x", "type": "value"},
|
|
292
|
+
0x19: {"name": "emitter spread y", "type": "value"},
|
|
293
|
+
0x1A: {"name": "emitter spread z", "type": "value"},
|
|
294
|
+
0x1B: {"name": "angular spread x", "type": "value"},
|
|
295
|
+
0x1C: {"name": "angular spread y", "type": "value"},
|
|
296
|
+
0x1D: {"name": "visual radius", "type": "value"},
|
|
297
|
+
0x1E: {"name": "cut-scene ignore", "type": "value"},
|
|
298
|
+
},
|
|
299
|
+
"MATH OPS": {
|
|
300
|
+
0x01: {"name": "Normalized Angle", "type": "value"},
|
|
301
|
+
},
|
|
302
|
+
"MemCard": {
|
|
303
|
+
0x01: {"name": "Unplugged", "type": "value"},
|
|
304
|
+
0x02: {"name": "Free Space", "type": "value"},
|
|
305
|
+
0x03: {"name": "Status", "type": "value"},
|
|
306
|
+
0x04: {"name": "Auto-Save", "type": "value"},
|
|
307
|
+
0x05: {"name": "Auto-Load", "type": "value"},
|
|
308
|
+
0x06: {"name": "Current Slot", "type": "value"},
|
|
309
|
+
0x07: {"name": "Current Slot Size", "type": "value"},
|
|
310
|
+
0x08: {"name": "Auto-Format", "type": "value"},
|
|
311
|
+
0x09: {"name": "Auto-Delete", "type": "value"},
|
|
312
|
+
0x0A: {"name": "Saved Language", "type": "value"},
|
|
313
|
+
0x0B: {"name": "Current Slot Percent Complete", "type": "value"},
|
|
314
|
+
0x0C: {"name": "Auto-Create", "type": "value"},
|
|
315
|
+
0x0D: {"name": "Current Slot Game Time", "type": "value"},
|
|
316
|
+
0x0E: {"name": "Changed State", "type": "value"},
|
|
317
|
+
},
|
|
318
|
+
"FOG": {
|
|
319
|
+
0x01: {"name": "vertex fog distance", "type": "value"},
|
|
320
|
+
0x02: {"name": "vertex fog color", "type": "value", "of": "color"},
|
|
321
|
+
0x03: {"name": "pixel fog/DOF distance", "type": "value"},
|
|
322
|
+
0x04: {"name": "pixel fog/DOF cutoff intensity", "type": "value"},
|
|
323
|
+
0x05: {"name": "pixel fog toggle", "type": "value"},
|
|
324
|
+
0x06: {"name": "DOF bluriness factor", "type": "value"},
|
|
325
|
+
0x07: {"name": "pixel fog/DOF color", "type": "value", "of": "color"},
|
|
326
|
+
0x08: {"name": "bloom toggle", "type": "value"},
|
|
327
|
+
0x09: {"name": "bloom add factor", "type": "value"},
|
|
328
|
+
0x0A: {"name": "bloom halo size", "type": "value"},
|
|
329
|
+
},
|
|
330
|
+
"rumble": {
|
|
331
|
+
0x01: {"name": "low frequency", "type": "value"},
|
|
332
|
+
0x02: {"name": "high frequency", "type": "value"},
|
|
333
|
+
0x03: {"name": "duration", "type": "value"},
|
|
334
|
+
0x04: {"name": "radius", "type": "value"},
|
|
335
|
+
0x05: {"name": "attenuation", "type": "value"},
|
|
336
|
+
0x06: {"name": "time remaining", "type": "value"},
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
SHOW_NAME_ONLY = True
|
|
341
|
+
TFBTOOL_MEMBER_FORMATTING = True
|
|
342
|
+
|
|
343
|
+
# Reference.scope -- which element a reference picks out of a set
|
|
344
|
+
# (0 = the set itself, no element picked).
|
|
345
|
+
SCOPE_LABELS = {
|
|
346
|
+
1: "first",
|
|
347
|
+
2: "last",
|
|
348
|
+
3: "random",
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Types that have no members of their own. A `sub` on one of these picks a
|
|
352
|
+
# component out of it (e.g. the x of a sprite's location pair), which is
|
|
353
|
+
# itself a plain value.
|
|
354
|
+
SCALAR_TYPES = {"value", "angle", "color", "position"}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def member_field(type_name: str | None, index: int) -> dict[str, str] | None:
|
|
358
|
+
"""The BINDINGS entry for field `index` of `type_name`, if it is known."""
|
|
359
|
+
bindings = BINDINGS.get(type_name) if type_name is not None else None
|
|
360
|
+
return bindings.get(index) if bindings is not None else None
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@dataclass(frozen=True)
|
|
364
|
+
class ResolvedType:
|
|
365
|
+
"""What a reference ultimately names: a type, and whether it is a whole
|
|
366
|
+
set of them or a single one."""
|
|
367
|
+
|
|
368
|
+
type: str | None # None when it could not be resolved
|
|
369
|
+
is_set: bool = False
|
|
370
|
+
|
|
371
|
+
@override
|
|
372
|
+
def __str__(self) -> str:
|
|
373
|
+
if self.type is None:
|
|
374
|
+
return "<unknown>"
|
|
375
|
+
return f"set::{self.type}" if self.is_set else self.type
|
|
376
|
+
|
|
377
|
+
def member_type(self, index: int) -> "ResolvedType":
|
|
378
|
+
"""This type's field `index` -- the type `.member`/`.sub` selects."""
|
|
379
|
+
field = member_field(self.type, index)
|
|
380
|
+
|
|
381
|
+
if field is None:
|
|
382
|
+
if self.type in SCALAR_TYPES:
|
|
383
|
+
return ResolvedType("value")
|
|
384
|
+
return ResolvedType(None)
|
|
385
|
+
|
|
386
|
+
if field.get("type") == "set":
|
|
387
|
+
return ResolvedType(field.get("of"), is_set=True)
|
|
388
|
+
|
|
389
|
+
# A plain value is the end of the line, but some of them are really a
|
|
390
|
+
# colour or an x/y pair and `sub` picks one component out: "of" says
|
|
391
|
+
# which table names those components, so `tint color`'s sub 0x04
|
|
392
|
+
# resolves to the `a` of BINDINGS["color"].
|
|
393
|
+
return ResolvedType(field.get("of", field.get("type"))) # "value"
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@dataclass
|
|
397
|
+
class Reference:
|
|
398
|
+
raw: int = 0x00 # the 32-bit little-endian value R, kept in sync by update_raw
|
|
399
|
+
index: int = 0 # R >> 14
|
|
400
|
+
member: int = 0 # (R >> 8) & 0x3F -- field within the target
|
|
401
|
+
scope: int = 0 # (R >> 6) & 3
|
|
402
|
+
sub: int = 0 # R & 0x3F
|
|
403
|
+
|
|
404
|
+
kind: ReferenceType = ReferenceType.NULL
|
|
405
|
+
|
|
406
|
+
builtin_kind: BuiltinType | None = (
|
|
407
|
+
None # resolved builtin kind (None for not builtin)
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
slot: int | None = None # table index used for the lookup (None for null/builtin)
|
|
411
|
+
entry: StringTableEntry | None = None # resolved entry (None for null/builtin)
|
|
412
|
+
|
|
413
|
+
context: "ParserContext | None" = None # script's parser context
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def createSimple_local(
|
|
417
|
+
cls,
|
|
418
|
+
local_refs: StringTable,
|
|
419
|
+
slot: int,
|
|
420
|
+
) -> "Reference":
|
|
421
|
+
index = slot + LOCAL_BASE
|
|
422
|
+
|
|
423
|
+
entry = local_refs.get(slot)
|
|
424
|
+
|
|
425
|
+
member = 0
|
|
426
|
+
scope = 0 # @1 @2 @3 (fist, last, random)
|
|
427
|
+
sub = 0
|
|
428
|
+
|
|
429
|
+
raw = (index << 14) | (member << 8) | (scope << 6) | sub
|
|
430
|
+
|
|
431
|
+
return cls(
|
|
432
|
+
raw=raw,
|
|
433
|
+
index=index,
|
|
434
|
+
member=0,
|
|
435
|
+
scope=0,
|
|
436
|
+
sub=0,
|
|
437
|
+
# --
|
|
438
|
+
kind=ReferenceType.LOCAL,
|
|
439
|
+
builtin_kind=None,
|
|
440
|
+
slot=slot,
|
|
441
|
+
entry=entry,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def __post_init__(self) -> None:
|
|
445
|
+
# Enable the __setattr__ hook only once every field exists, then compose
|
|
446
|
+
# `raw` from the values the caller passed.
|
|
447
|
+
object.__setattr__(self, "_raw_sync", True)
|
|
448
|
+
self.update_raw()
|
|
449
|
+
|
|
450
|
+
@override
|
|
451
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
452
|
+
# dataclass __init__ assigns fields one at a time, so the _raw_sync guard
|
|
453
|
+
# keeps us from composing a raw out of half-initialised state;
|
|
454
|
+
# __post_init__ does that once at the end.
|
|
455
|
+
syncing = name in _RAW_INPUTS and getattr(self, "_raw_sync", False)
|
|
456
|
+
previous = getattr(self, name) if syncing else None
|
|
457
|
+
|
|
458
|
+
super().__setattr__(name, value)
|
|
459
|
+
|
|
460
|
+
if syncing:
|
|
461
|
+
try:
|
|
462
|
+
self.update_raw()
|
|
463
|
+
except ValueError:
|
|
464
|
+
object.__setattr__(self, name, previous) # keep the object valid
|
|
465
|
+
raise
|
|
466
|
+
|
|
467
|
+
def update_raw(self) -> int:
|
|
468
|
+
"""Recompose `raw` (and `index`) from the current field values.
|
|
469
|
+
|
|
470
|
+
`index` is derived from `kind` plus `slot`/`builtin_kind` whenever those
|
|
471
|
+
say what it should be, so moving a reference to another table slot is
|
|
472
|
+
enough to keep both `index` and `raw` right. Returns the new `raw`.
|
|
473
|
+
"""
|
|
474
|
+
index = self.index
|
|
475
|
+
|
|
476
|
+
if self.kind == ReferenceType.NULL:
|
|
477
|
+
# A null reference is all-ones; that is what the bit-fields decode to.
|
|
478
|
+
index, member, scope, sub = 0x3FFFF, 0x3F, 3, 0x3F
|
|
479
|
+
else:
|
|
480
|
+
member, scope, sub = self.member, self.scope, self.sub
|
|
481
|
+
|
|
482
|
+
if self.kind == ReferenceType.BUILTIN and self.builtin_kind is not None:
|
|
483
|
+
index = self.builtin_kind.value
|
|
484
|
+
elif self.kind == ReferenceType.LOCAL and self.slot is not None:
|
|
485
|
+
index = LOCAL_BASE + self.slot
|
|
486
|
+
elif self.kind == ReferenceType.GLOBAL and self.slot is not None:
|
|
487
|
+
index = self.slot
|
|
488
|
+
|
|
489
|
+
for name, value, limit in (
|
|
490
|
+
("index", index, 0x3FFFF),
|
|
491
|
+
("member", member, 0x3F),
|
|
492
|
+
("scope", scope, 0x3),
|
|
493
|
+
("sub", sub, 0x3F),
|
|
494
|
+
):
|
|
495
|
+
if not 0 <= value <= limit:
|
|
496
|
+
raise ValueError(
|
|
497
|
+
f"reference {name} {value:#x} does not fit its bit-field "
|
|
498
|
+
+ f"(0..{limit:#x})"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
raw = (index << 14) | (member << 8) | (scope << 6) | sub
|
|
502
|
+
|
|
503
|
+
# object.__setattr__, so writing these back does not re-enter update_raw.
|
|
504
|
+
object.__setattr__(self, "index", index)
|
|
505
|
+
object.__setattr__(self, "member", member)
|
|
506
|
+
object.__setattr__(self, "scope", scope)
|
|
507
|
+
object.__setattr__(self, "sub", sub)
|
|
508
|
+
object.__setattr__(self, "raw", raw)
|
|
509
|
+
|
|
510
|
+
return raw
|
|
511
|
+
|
|
512
|
+
@classmethod
|
|
513
|
+
def createSimple_global(
|
|
514
|
+
cls,
|
|
515
|
+
global_refs: StringTable,
|
|
516
|
+
slot: int,
|
|
517
|
+
) -> "Reference":
|
|
518
|
+
index = slot
|
|
519
|
+
|
|
520
|
+
entry = global_refs.get(slot)
|
|
521
|
+
|
|
522
|
+
member = 0
|
|
523
|
+
scope = 0 # @1 @2 @3 (fist, last, random)
|
|
524
|
+
sub = 0
|
|
525
|
+
|
|
526
|
+
raw = (index << 14) | (member << 8) | (scope << 6) | sub
|
|
527
|
+
|
|
528
|
+
return cls(
|
|
529
|
+
raw=raw,
|
|
530
|
+
index=index,
|
|
531
|
+
member=0,
|
|
532
|
+
scope=0,
|
|
533
|
+
sub=0,
|
|
534
|
+
# --
|
|
535
|
+
kind=ReferenceType.GLOBAL,
|
|
536
|
+
builtin_kind=None,
|
|
537
|
+
slot=slot,
|
|
538
|
+
entry=entry,
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
@classmethod
|
|
542
|
+
def createSimple_builtin(
|
|
543
|
+
cls,
|
|
544
|
+
builtin_type: BuiltinType,
|
|
545
|
+
) -> "Reference":
|
|
546
|
+
index = builtin_type.value
|
|
547
|
+
|
|
548
|
+
member = 0
|
|
549
|
+
scope = 0 # @1 @2 @3 (fist, last, random)
|
|
550
|
+
sub = 0
|
|
551
|
+
|
|
552
|
+
raw = (index << 14) | (member << 8) | (scope << 6) | sub
|
|
553
|
+
|
|
554
|
+
return cls(
|
|
555
|
+
raw=raw,
|
|
556
|
+
index=index,
|
|
557
|
+
member=0,
|
|
558
|
+
scope=0,
|
|
559
|
+
sub=0,
|
|
560
|
+
# --
|
|
561
|
+
kind=ReferenceType.BUILTIN,
|
|
562
|
+
builtin_kind=builtin_type,
|
|
563
|
+
slot=None,
|
|
564
|
+
entry=None,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
@classmethod
|
|
568
|
+
def read(
|
|
569
|
+
cls,
|
|
570
|
+
reader: "BinaryReader",
|
|
571
|
+
global_refs: StringTable,
|
|
572
|
+
local_refs: StringTable,
|
|
573
|
+
context: "ParserContext | None" = None,
|
|
574
|
+
) -> "Reference":
|
|
575
|
+
"""Parse a 4-byte TFB-Script variable reference.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
reader: BinaryReader positioned at the reference.
|
|
579
|
+
global_refs: the script's 2nd string table (globals) for name resolution.
|
|
580
|
+
local_refs: the script's 3rd string table (locals) for name resolution.
|
|
581
|
+
context: the script's parser context, used to resolve the type of
|
|
582
|
+
some builtins (e.g. "[~subset]") from prior opcodes.
|
|
583
|
+
Returns:
|
|
584
|
+
A Reference with the decoded bit-fields and a resolved `name`.
|
|
585
|
+
"""
|
|
586
|
+
if reader.size_remaining() < 4:
|
|
587
|
+
raise ValueError("a reference is 4 bytes, not enough bytes remaining")
|
|
588
|
+
|
|
589
|
+
raw = reader.read_u32()
|
|
590
|
+
fields = {
|
|
591
|
+
"raw": raw,
|
|
592
|
+
"index": raw >> 0xE,
|
|
593
|
+
"member": (raw >> 0x8) & 0x3F, # member index (applied first)
|
|
594
|
+
"scope": (raw >> 0x6) & 3, # mode
|
|
595
|
+
"sub": raw & 0x3F, # member index (applied last)
|
|
596
|
+
}
|
|
597
|
+
index = fields["index"]
|
|
598
|
+
|
|
599
|
+
if raw == NULL_REF:
|
|
600
|
+
return cls(
|
|
601
|
+
**fields,
|
|
602
|
+
builtin_kind=None,
|
|
603
|
+
kind=ReferenceType.NULL,
|
|
604
|
+
slot=None,
|
|
605
|
+
entry=None,
|
|
606
|
+
context=context,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if index >= BUILTIN_BASE:
|
|
610
|
+
return cls(
|
|
611
|
+
**fields,
|
|
612
|
+
kind=ReferenceType.BUILTIN,
|
|
613
|
+
builtin_kind=BuiltinType(index),
|
|
614
|
+
slot=None,
|
|
615
|
+
entry=None,
|
|
616
|
+
context=context,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
if index < LOCAL_BASE:
|
|
620
|
+
entry = global_refs.get(index)
|
|
621
|
+
return cls(
|
|
622
|
+
**fields,
|
|
623
|
+
kind=ReferenceType.GLOBAL,
|
|
624
|
+
builtin_kind=None,
|
|
625
|
+
slot=index,
|
|
626
|
+
entry=entry,
|
|
627
|
+
context=context,
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
slot = index - LOCAL_BASE
|
|
631
|
+
entry = local_refs.get(slot)
|
|
632
|
+
return cls(
|
|
633
|
+
**fields,
|
|
634
|
+
kind=ReferenceType.LOCAL,
|
|
635
|
+
builtin_kind=None,
|
|
636
|
+
slot=slot,
|
|
637
|
+
entry=entry,
|
|
638
|
+
context=context,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
def write(self, f: BinaryIO) -> None:
|
|
642
|
+
if self.kind == ReferenceType.BUILTIN and self.builtin_kind is None:
|
|
643
|
+
raise ValueError("builtin reference has no builtin_kind")
|
|
644
|
+
if self.kind in (ReferenceType.GLOBAL, ReferenceType.LOCAL):
|
|
645
|
+
if self.slot is None:
|
|
646
|
+
raise ValueError(f"{self.kind.name.lower()} reference has no slot")
|
|
647
|
+
elif self.kind not in (ReferenceType.NULL, ReferenceType.BUILTIN):
|
|
648
|
+
raise ValueError(f"Reference has unknown kind {self.kind}")
|
|
649
|
+
|
|
650
|
+
write_u32(f, self.update_raw())
|
|
651
|
+
|
|
652
|
+
@override
|
|
653
|
+
def __str__(self) -> str:
|
|
654
|
+
if self.kind == ReferenceType.NULL: # Never seen a use of that ¯\_(ツ)_/¯
|
|
655
|
+
return "<null>"
|
|
656
|
+
|
|
657
|
+
if self.kind == ReferenceType.GLOBAL:
|
|
658
|
+
assert self.entry is not None
|
|
659
|
+
s = variable_global(
|
|
660
|
+
self.entry.name if SHOW_NAME_ONLY else self.entry.string
|
|
661
|
+
)
|
|
662
|
+
if self.entry.category == "set":
|
|
663
|
+
s = f" [{s}]"
|
|
664
|
+
|
|
665
|
+
elif self.kind == ReferenceType.LOCAL:
|
|
666
|
+
assert self.entry is not None
|
|
667
|
+
s = variable(self.entry.name if SHOW_NAME_ONLY else self.entry.string)
|
|
668
|
+
if self.entry.category == "set":
|
|
669
|
+
s = f" [{s}]"
|
|
670
|
+
|
|
671
|
+
elif self.kind == ReferenceType.BUILTIN:
|
|
672
|
+
assert (
|
|
673
|
+
self.builtin_kind is not None
|
|
674
|
+
) # should never be None for BUILTIN kind
|
|
675
|
+
|
|
676
|
+
if TFBTOOL_MEMBER_FORMATTING and self.builtin_kind == BuiltinType.SELF:
|
|
677
|
+
if self.member or self.sub:
|
|
678
|
+
s = builtin("my")
|
|
679
|
+
else:
|
|
680
|
+
s = builtin("myself")
|
|
681
|
+
else:
|
|
682
|
+
s = builtin(BUILTIN_LABELS[self.builtin_kind])
|
|
683
|
+
|
|
684
|
+
else:
|
|
685
|
+
raise ValueError(f"Reference has unknown kind {self.kind}")
|
|
686
|
+
|
|
687
|
+
if self.member:
|
|
688
|
+
if self.builtin_kind == BuiltinType.MESSAGE_VALUE:
|
|
689
|
+
# message value will always resolve to "value" wich is not in the bindings, so we suppress the warning here because its intended behavior
|
|
690
|
+
s += self._get_member_string(suppressWarnings=True) or ""
|
|
691
|
+
else:
|
|
692
|
+
s += self._get_member_string() or ""
|
|
693
|
+
|
|
694
|
+
if self.scope:
|
|
695
|
+
# On a set, the scope picks one element out of it, e.g.
|
|
696
|
+
# "first in [players]". `member` above still selects the set
|
|
697
|
+
# itself, while `sub` below applies to the picked element. What it
|
|
698
|
+
# means on anything else is unknown, so leave those as a raw "@N".
|
|
699
|
+
if self._selects_from_set():
|
|
700
|
+
label = SCOPE_LABELS.get(self.scope, str(self.scope))
|
|
701
|
+
s = f"{keyword(f'{label} in')} {s.lstrip(' ')}"
|
|
702
|
+
else:
|
|
703
|
+
s += f"@{self.scope}"
|
|
704
|
+
|
|
705
|
+
if self.sub:
|
|
706
|
+
s += self._get_sub_string()
|
|
707
|
+
|
|
708
|
+
return s
|
|
709
|
+
|
|
710
|
+
def _get_sub_string(self) -> str:
|
|
711
|
+
"""The component `sub` picks out, named when whatever it applies to has
|
|
712
|
+
a name for it -- the `a` of a tint colour, the `x` of a sprite's
|
|
713
|
+
location -- and the raw index when it does not."""
|
|
714
|
+
try:
|
|
715
|
+
applies_to = self.resolve_type(apply_sub=False).type
|
|
716
|
+
except ValueError:
|
|
717
|
+
applies_to = None # a builtin with no producer in reach
|
|
718
|
+
|
|
719
|
+
field = member_field(applies_to, self.sub)
|
|
720
|
+
|
|
721
|
+
if field is None or field.get("name") is None:
|
|
722
|
+
return f".sub[{self.sub:#04x}]"
|
|
723
|
+
|
|
724
|
+
return f".{field['name']}"
|
|
725
|
+
|
|
726
|
+
def _selects_from_set(self) -> bool:
|
|
727
|
+
"""Whether what `scope` picks from is a set: the member it selects, or,
|
|
728
|
+
with no member, the reference's own target."""
|
|
729
|
+
if self.member:
|
|
730
|
+
my_field = member_field(self.get_resolved_type(), self.member)
|
|
731
|
+
return my_field is not None and my_field.get("type") == "set"
|
|
732
|
+
|
|
733
|
+
return self._target_is_set()
|
|
734
|
+
|
|
735
|
+
def _target_is_set(self) -> bool:
|
|
736
|
+
"""Whether the reference's own target (before `member`) is a set."""
|
|
737
|
+
if self.kind == ReferenceType.BUILTIN:
|
|
738
|
+
return self.builtin_kind == BuiltinType.SUBSET
|
|
739
|
+
|
|
740
|
+
return self.entry is not None and self.entry.category == "set"
|
|
741
|
+
|
|
742
|
+
def _get_member_string(self, suppressWarnings=False) -> str | None:
|
|
743
|
+
my_type = self.get_resolved_type()
|
|
744
|
+
|
|
745
|
+
if my_type is not None:
|
|
746
|
+
my_bindings = BINDINGS.get(my_type)
|
|
747
|
+
|
|
748
|
+
if my_bindings is not None:
|
|
749
|
+
my_field = member_field(my_type, self.member)
|
|
750
|
+
|
|
751
|
+
if my_field is not None:
|
|
752
|
+
label = my_field.get("name")
|
|
753
|
+
type_ = my_field.get("type")
|
|
754
|
+
|
|
755
|
+
if TFBTOOL_MEMBER_FORMATTING:
|
|
756
|
+
if (
|
|
757
|
+
self.kind == ReferenceType.BUILTIN
|
|
758
|
+
and self.builtin_kind == BuiltinType.SELF
|
|
759
|
+
):
|
|
760
|
+
if type_ == "set":
|
|
761
|
+
return f" [{label}]"
|
|
762
|
+
else:
|
|
763
|
+
return f" {label}"
|
|
764
|
+
else:
|
|
765
|
+
if type_ == "set":
|
|
766
|
+
return f"'s [{label}]"
|
|
767
|
+
else:
|
|
768
|
+
return f"'s {label}"
|
|
769
|
+
|
|
770
|
+
else:
|
|
771
|
+
if type_ == "set":
|
|
772
|
+
return f".[{label}]"
|
|
773
|
+
else:
|
|
774
|
+
return f".{label}"
|
|
775
|
+
else:
|
|
776
|
+
if not suppressWarnings:
|
|
777
|
+
print(
|
|
778
|
+
"WARN: cant resolve binding for",
|
|
779
|
+
my_type,
|
|
780
|
+
"member",
|
|
781
|
+
hex(self.member),
|
|
782
|
+
"entry",
|
|
783
|
+
self.entry,
|
|
784
|
+
file=sys.stderr,
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
else:
|
|
788
|
+
if not suppressWarnings:
|
|
789
|
+
print(
|
|
790
|
+
"WARN: cant resolve bindings for type",
|
|
791
|
+
'"' + my_type + '"',
|
|
792
|
+
"entry",
|
|
793
|
+
self.entry,
|
|
794
|
+
"field",
|
|
795
|
+
hex(self.member),
|
|
796
|
+
file=sys.stderr,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
return f".field[{self.member:#04x}]"
|
|
800
|
+
|
|
801
|
+
def resolve_type(self, apply_sub: bool = True) -> ResolvedType:
|
|
802
|
+
"""The type this reference *ultimately* names, following the whole
|
|
803
|
+
chain: target -> `member` -> `scope` -> `sub`.
|
|
804
|
+
|
|
805
|
+
e.g. "first in return point's [waypoints]" starts at the actor
|
|
806
|
+
variable "return point", takes its `waypoints` member (a set of
|
|
807
|
+
waypoints), then `scope` picks one out of it -> a single "waypoint".
|
|
808
|
+
|
|
809
|
+
`get_resolved_type` only gives the type of the target the chain starts
|
|
810
|
+
at ("actor" in that example), which is what a `member` index is looked
|
|
811
|
+
up against.
|
|
812
|
+
|
|
813
|
+
`apply_sub=False` stops before `sub`, for the ops where `sub` is not a
|
|
814
|
+
member selection: on a "find subset" set it names the field to filter
|
|
815
|
+
on, so "my [waypoints].sub[0x01]" is still a set of waypoints there.
|
|
816
|
+
"""
|
|
817
|
+
resolved = ResolvedType(self.get_resolved_type(), self._target_is_set())
|
|
818
|
+
|
|
819
|
+
if self.member:
|
|
820
|
+
# NOTE: a member on a set variable applies to its elements, so the
|
|
821
|
+
# member's own kind is what we report -- same as `_selects_from_set`.
|
|
822
|
+
resolved = resolved.member_type(self.member)
|
|
823
|
+
|
|
824
|
+
if self.scope and resolved.is_set:
|
|
825
|
+
# The scope picks one element out of the set (first/last/random).
|
|
826
|
+
# On anything else its meaning is unknown, so leave the type alone.
|
|
827
|
+
resolved = ResolvedType(resolved.type)
|
|
828
|
+
|
|
829
|
+
if self.sub and apply_sub:
|
|
830
|
+
resolved = resolved.member_type(self.sub)
|
|
831
|
+
|
|
832
|
+
return resolved
|
|
833
|
+
|
|
834
|
+
def get_final_type(self, apply_sub: bool = True) -> str | None:
|
|
835
|
+
"""The name of the type `resolve_type` lands on, e.g. "waypoint"."""
|
|
836
|
+
return self.resolve_type(apply_sub).type
|
|
837
|
+
|
|
838
|
+
def get_resolved_type(self) -> str | None:
|
|
839
|
+
"""The type of the target this reference starts at, *before* `member`,
|
|
840
|
+
`scope` and `sub` are applied. Use `resolve_type` for the end of the
|
|
841
|
+
chain."""
|
|
842
|
+
# A builtin resolves through the op that produced it, and that op's own
|
|
843
|
+
# operand can be the same builtin again -- `control [~controlled]`
|
|
844
|
+
# nested inside `control [~controlled]` resolves via
|
|
845
|
+
# nearest_ancestor(OpControl), which hands back the same producer every
|
|
846
|
+
# time, so the chain never advances. Guard re-entry and report the type
|
|
847
|
+
# as unknown rather than recursing forever.
|
|
848
|
+
if id(self) in _RESOLVING:
|
|
849
|
+
return None
|
|
850
|
+
|
|
851
|
+
_RESOLVING.add(id(self))
|
|
852
|
+
try:
|
|
853
|
+
return self._get_resolved_type()
|
|
854
|
+
finally:
|
|
855
|
+
_RESOLVING.discard(id(self))
|
|
856
|
+
|
|
857
|
+
def _get_resolved_type(self) -> str | None:
|
|
858
|
+
if self.kind == ReferenceType.NULL:
|
|
859
|
+
return "null"
|
|
860
|
+
|
|
861
|
+
if self.kind == ReferenceType.BUILTIN:
|
|
862
|
+
if self.builtin_kind == BuiltinType.SELF:
|
|
863
|
+
return "actor"
|
|
864
|
+
elif self.builtin_kind == BuiltinType.SUBSET:
|
|
865
|
+
from tfbscript.opcodes import OpCheckFOV, OpFindSubset
|
|
866
|
+
|
|
867
|
+
producer = self._builtin_find_producer((OpCheckFOV, OpFindSubset))
|
|
868
|
+
|
|
869
|
+
if isinstance(producer, OpFindSubset):
|
|
870
|
+
return producer.set_ref.get_final_type(apply_sub=False)
|
|
871
|
+
else:
|
|
872
|
+
return producer.target_ref.get_final_type()
|
|
873
|
+
|
|
874
|
+
elif self.builtin_kind == BuiltinType.MESSAGE_VALUE:
|
|
875
|
+
return "value" # TODO: maybe we can resolve here??? seems kinda hard to do, since the message value is set by the sender and we don't have that context here
|
|
876
|
+
|
|
877
|
+
elif self.builtin_kind == BuiltinType.FOUND_VARIABLE:
|
|
878
|
+
# Not tested on real scripts cus couldnt find one
|
|
879
|
+
from tfbscript.opcodes import OpFindVariable
|
|
880
|
+
|
|
881
|
+
producer = self._builtin_find_producer(OpFindVariable)
|
|
882
|
+
return producer.var_ref.get_final_type()
|
|
883
|
+
|
|
884
|
+
elif self.builtin_kind == BuiltinType.EACH:
|
|
885
|
+
from tfbscript.opcodes import OpForEach
|
|
886
|
+
|
|
887
|
+
producer = self._builtin_find_producer(OpForEach)
|
|
888
|
+
return producer.set_ref.get_final_type()
|
|
889
|
+
|
|
890
|
+
elif self.builtin_kind == BuiltinType.CONTROLLED:
|
|
891
|
+
from tfbscript.opcodes import OpControl, OpSpawnActor
|
|
892
|
+
|
|
893
|
+
producer = self._builtin_find_producer((OpControl, OpSpawnActor))
|
|
894
|
+
|
|
895
|
+
if isinstance(producer, OpControl):
|
|
896
|
+
return producer.target.get_final_type()
|
|
897
|
+
else:
|
|
898
|
+
return producer.clone_ref.get_final_type()
|
|
899
|
+
|
|
900
|
+
else:
|
|
901
|
+
raise ValueError(
|
|
902
|
+
f"Reference is a builtin of kind {self.builtin_kind} but has no type resolution implemented"
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
if self.kind in (ReferenceType.GLOBAL, ReferenceType.LOCAL): # variable
|
|
906
|
+
if self.entry is not None:
|
|
907
|
+
return self.entry.type
|
|
908
|
+
else:
|
|
909
|
+
raise ValueError("Reference has no entry to resolve type")
|
|
910
|
+
|
|
911
|
+
return None # Should never reach here
|
|
912
|
+
|
|
913
|
+
def _builtin_find_producer(
|
|
914
|
+
self, opcode_types: type[_OpcodeT] | tuple[type[_OpcodeT], ...]
|
|
915
|
+
):
|
|
916
|
+
if self.context is None:
|
|
917
|
+
raise ValueError(
|
|
918
|
+
f"Reference {self.builtin_kind} is a builtin (and a field on it is used) but has no context to resolve its type"
|
|
919
|
+
)
|
|
920
|
+
producer = self.context.nearest_ancestor(opcode_types)
|
|
921
|
+
if producer is None:
|
|
922
|
+
raise ValueError(
|
|
923
|
+
f"Reference {self.builtin_kind} is a builtin (and a field on it is used) but has no enclosing op to resolve its type"
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
return producer
|