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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import method, parentheses
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import MembershipCombiner
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@opcode("change membership")
|
|
13
|
+
@dataclass
|
|
14
|
+
class OpChangeMembership(Opcode):
|
|
15
|
+
ref: Reference = field(default_factory=Reference)
|
|
16
|
+
membershipCombiner: MembershipCombiner = field(default=MembershipCombiner.add)
|
|
17
|
+
ref2: Reference = field(default_factory=Reference)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
@override
|
|
21
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpChangeMembership":
|
|
22
|
+
ref = reader.readRef()
|
|
23
|
+
membershipCombiner = MembershipCombiner(reader.read_u8())
|
|
24
|
+
ref2 = reader.readRef()
|
|
25
|
+
return cls(ref=ref, membershipCombiner=membershipCombiner, ref2=ref2)
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
def source_line(self, inline: bool = False) -> str:
|
|
29
|
+
return f"{self.ref}.{method(self.membershipCombiner.symbol())}{parentheses('(')}{self.ref2}{parentheses(')')};"
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
33
|
+
self.ref.write(f)
|
|
34
|
+
write_u8(f, self.membershipCombiner)
|
|
35
|
+
self.ref2.write(f)
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
39
|
+
return {
|
|
40
|
+
"fields": [
|
|
41
|
+
{
|
|
42
|
+
"type": "op-label",
|
|
43
|
+
"value": "set [set]",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "ref",
|
|
47
|
+
"ref": self.ref,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "label",
|
|
51
|
+
"content": "to",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "enum",
|
|
55
|
+
"value": self.membershipCombiner,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "ref",
|
|
59
|
+
"ref": self.ref2,
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import comparison, keyword, number
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import CheckFOVMode, RelOp
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
from tfbscript.rhs import Rhs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@opcode(
|
|
14
|
+
"check fov",
|
|
15
|
+
"""
|
|
16
|
+
Is there an actor from target_ref within arc_width°
|
|
17
|
+
of angle_base, whose distance to me satisfies
|
|
18
|
+
"distance {range_relop} range",
|
|
19
|
+
and if mode == consider_obstructions,
|
|
20
|
+
i have a clear line of sight to them?
|
|
21
|
+
""",
|
|
22
|
+
)
|
|
23
|
+
@dataclass
|
|
24
|
+
class OpCheckFOV(Opcode):
|
|
25
|
+
angle_base: Rhs = field(default_factory=Rhs)
|
|
26
|
+
arc_width: Rhs = field(default_factory=Rhs)
|
|
27
|
+
|
|
28
|
+
target_ref: Reference = field(default_factory=Reference)
|
|
29
|
+
|
|
30
|
+
range_relop: RelOp = RelOp.Eq
|
|
31
|
+
range: Rhs = field(default_factory=Rhs)
|
|
32
|
+
|
|
33
|
+
mode: CheckFOVMode = CheckFOVMode.ignore_obstructions
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
@override
|
|
37
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCheckFOV":
|
|
38
|
+
angle_base = reader.readRHS()
|
|
39
|
+
arc_width = reader.readRHS()
|
|
40
|
+
|
|
41
|
+
target_ref = reader.readRef()
|
|
42
|
+
|
|
43
|
+
range_relop = RelOp(reader.read_u8())
|
|
44
|
+
range = reader.readRHS()
|
|
45
|
+
|
|
46
|
+
checkfov_mode = CheckFOVMode(reader.read_u8())
|
|
47
|
+
|
|
48
|
+
return cls(
|
|
49
|
+
angle_base=angle_base,
|
|
50
|
+
arc_width=arc_width,
|
|
51
|
+
target_ref=target_ref,
|
|
52
|
+
range_relop=range_relop,
|
|
53
|
+
range=range,
|
|
54
|
+
mode=checkfov_mode,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
def source_line(self, inline: bool = False) -> str:
|
|
59
|
+
condition = f"{self.target_ref} is within a {number(self.arc_width)}° cone centered on heading {number(self.angle_base)}, distance {comparison(self.range_relop.symbol())} {self.range}, {self.mode}"
|
|
60
|
+
|
|
61
|
+
# Normally we would have a check for inline here,
|
|
62
|
+
# but its nice to know that this is a FOV check
|
|
63
|
+
|
|
64
|
+
return f"{keyword('check fov (')} {condition} {keyword(')')}"
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
68
|
+
self.angle_base.write(f)
|
|
69
|
+
self.arc_width.write(f)
|
|
70
|
+
self.target_ref.write(f)
|
|
71
|
+
write_u8(f, self.range_relop)
|
|
72
|
+
self.range.write(f)
|
|
73
|
+
write_u8(f, self.mode)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import comparison, keyword
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import MembershipTest
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@opcode("check membership")
|
|
13
|
+
@dataclass
|
|
14
|
+
class OpCheckMembership(Opcode):
|
|
15
|
+
ref1: Reference = field(default_factory=Reference)
|
|
16
|
+
membershipTest: MembershipTest = MembershipTest.includes
|
|
17
|
+
ref2: Reference = field(default_factory=Reference)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
@override
|
|
21
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCheckMembership":
|
|
22
|
+
ref1 = reader.readRef()
|
|
23
|
+
membershipTest = MembershipTest(reader.read_u8())
|
|
24
|
+
ref2 = reader.readRef()
|
|
25
|
+
return cls(ref1=ref1, membershipTest=membershipTest, ref2=ref2)
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
def source_line(self, inline: bool = False) -> str:
|
|
29
|
+
condition = (
|
|
30
|
+
f"{self.ref1} {comparison(self.membershipTest.symbol())} {self.ref2}"
|
|
31
|
+
)
|
|
32
|
+
if inline:
|
|
33
|
+
return condition
|
|
34
|
+
|
|
35
|
+
return f"{keyword('check membership (')} {condition} {keyword(')')}"
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
39
|
+
self.ref1.write(f)
|
|
40
|
+
write_u8(f, self.membershipTest)
|
|
41
|
+
self.ref2.write(f)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import keyword
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import RelOp
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
from tfbscript.rhs import Rhs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@opcode("check message")
|
|
14
|
+
@dataclass
|
|
15
|
+
class OpCheckMessage(Opcode):
|
|
16
|
+
message_ref: Reference = field(default_factory=Reference)
|
|
17
|
+
|
|
18
|
+
sender_ref: Reference = field(default_factory=Reference)
|
|
19
|
+
rel_op: RelOp = field(default=RelOp.Eq)
|
|
20
|
+
value: Rhs = field(default_factory=Rhs)
|
|
21
|
+
|
|
22
|
+
_is_extended: bool = field(
|
|
23
|
+
default=False
|
|
24
|
+
) # has sender_ref, rel_op, value determined if payload > 4
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
@override
|
|
28
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCheckMessage":
|
|
29
|
+
message_ref = reader.readRef()
|
|
30
|
+
|
|
31
|
+
# Three shapes only, 4 / 14 / 20 bytes, so this threshold cannot be
|
|
32
|
+
# straddled. The engine's own test is not reproducible from the .ai:
|
|
33
|
+
# parse (FUN_0043AE30) asks the message's descriptor
|
|
34
|
+
# DAT_0061f870->vt[0x18]() and stops right after the message ref when
|
|
35
|
+
# that is <= 0 -- an operand count that comes from the message's
|
|
36
|
+
# per-level declaration in the level hub, not from this file. The two
|
|
37
|
+
# `message` vtables differ only there: 005CDC08 returns 0, 005CE590
|
|
38
|
+
# returns 2. Verified as a per-declaration property: of 88 message
|
|
39
|
+
# symbols, 79 are always extended and 8 always plain; the one that is
|
|
40
|
+
# both (`ledge nearby`) is plain in 8 levels and extended in `battle`.
|
|
41
|
+
#
|
|
42
|
+
# Ref: 4b
|
|
43
|
+
# RelOp: 1b
|
|
44
|
+
# Rhs: min. 5b
|
|
45
|
+
if reader.size_remaining() >= 4 + 1 + 5:
|
|
46
|
+
sender_ref = reader.readRef()
|
|
47
|
+
|
|
48
|
+
rel_op = RelOp(reader.read_u8())
|
|
49
|
+
value = reader.readRHS()
|
|
50
|
+
|
|
51
|
+
return cls(
|
|
52
|
+
message_ref=message_ref,
|
|
53
|
+
sender_ref=sender_ref,
|
|
54
|
+
rel_op=rel_op,
|
|
55
|
+
value=value,
|
|
56
|
+
_is_extended=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return cls(
|
|
60
|
+
message_ref=message_ref,
|
|
61
|
+
_is_extended=False,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@override
|
|
65
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
66
|
+
self.message_ref.write(f)
|
|
67
|
+
if not self._is_extended:
|
|
68
|
+
return
|
|
69
|
+
self.sender_ref.write(f)
|
|
70
|
+
write_u8(f, self.rel_op)
|
|
71
|
+
self.value.write(f)
|
|
72
|
+
|
|
73
|
+
@override
|
|
74
|
+
def source_line(self, inline: bool = False) -> str:
|
|
75
|
+
if self._is_extended:
|
|
76
|
+
condition = f"value {self.rel_op} {self.value}"
|
|
77
|
+
return f"{keyword('check message (')} {self.message_ref}, sent by: {self.sender_ref}, where: {condition} {keyword(')')}"
|
|
78
|
+
else:
|
|
79
|
+
return f"{keyword('check message (')} {self.message_ref} {keyword(')')}"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import keyword
|
|
5
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
6
|
+
from tfbscript.payload import PayloadReader
|
|
7
|
+
from tfbscript.reference import Reference
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@opcode("check reference")
|
|
11
|
+
@dataclass
|
|
12
|
+
class OpCheckReference(Opcode):
|
|
13
|
+
ref1: Reference = field(default_factory=Reference)
|
|
14
|
+
ref2: Reference = field(default_factory=Reference)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
@override
|
|
18
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCheckReference":
|
|
19
|
+
ref1 = reader.readRef()
|
|
20
|
+
ref2 = reader.readRef()
|
|
21
|
+
|
|
22
|
+
return cls(ref1=ref1, ref2=ref2)
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
26
|
+
self.ref1.write(f)
|
|
27
|
+
self.ref2.write(f)
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def source_line(self, inline: bool = False) -> str:
|
|
31
|
+
condition = f"*{self.ref1} is reference to {self.ref2}"
|
|
32
|
+
if inline:
|
|
33
|
+
return condition
|
|
34
|
+
|
|
35
|
+
return f"{keyword('check reference (')} {condition} {keyword(')')}"
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
39
|
+
return {
|
|
40
|
+
"hasBody": True,
|
|
41
|
+
"fields": [
|
|
42
|
+
{
|
|
43
|
+
"type": "op-label",
|
|
44
|
+
"value": "check reference",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "ref",
|
|
48
|
+
"name": "ref1",
|
|
49
|
+
"ref": self.ref1,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "op-label",
|
|
53
|
+
"value": "is reference to",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "ref",
|
|
57
|
+
"name": "ref2",
|
|
58
|
+
"ref": self.ref2,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import comparison, keyword
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import RelOp
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
from tfbscript.rhs import Rhs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@opcode("check value")
|
|
14
|
+
@dataclass
|
|
15
|
+
class OpCheckValue(Opcode):
|
|
16
|
+
lhs: Reference = field(default_factory=Reference)
|
|
17
|
+
rel_op: RelOp = RelOp.Eq
|
|
18
|
+
rhs: Rhs = field(default_factory=Rhs)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
@override
|
|
22
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCheckValue":
|
|
23
|
+
lhs = reader.readRef()
|
|
24
|
+
rel_op = RelOp(reader.read_u8())
|
|
25
|
+
rhs = reader.readRHS()
|
|
26
|
+
return cls(lhs=lhs, rel_op=rel_op, rhs=rhs)
|
|
27
|
+
|
|
28
|
+
@override
|
|
29
|
+
def source_line(self, inline: bool = False) -> str:
|
|
30
|
+
condition = f"{self.lhs} {comparison(self.rel_op.symbol())} {self.rhs}"
|
|
31
|
+
if inline:
|
|
32
|
+
return condition
|
|
33
|
+
|
|
34
|
+
return f"{keyword('check value (')} {condition} {keyword(')')}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
39
|
+
return {
|
|
40
|
+
"hasBody": True,
|
|
41
|
+
"fields": [
|
|
42
|
+
{
|
|
43
|
+
"type": "op-label",
|
|
44
|
+
"value": "check value",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "ref",
|
|
48
|
+
"ref": self.lhs,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "enum",
|
|
52
|
+
"name": "rel_op",
|
|
53
|
+
"entry": self.rel_op,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "rhs",
|
|
57
|
+
"rhs": self.rhs,
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
64
|
+
self.lhs.write(f)
|
|
65
|
+
write_u8(f, self.rel_op.value)
|
|
66
|
+
self.rhs.write(f)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import comment
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.payload import PayloadReader
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@opcode("comment:")
|
|
11
|
+
@dataclass
|
|
12
|
+
class OpComment(Opcode):
|
|
13
|
+
content: str = ""
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
@override
|
|
17
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpComment":
|
|
18
|
+
content = reader.read_string(reader.read_u8())
|
|
19
|
+
return cls(content=content)
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def source_line(self, inline: bool = False) -> str:
|
|
23
|
+
return comment(f"// {self.content}")
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"fields": [
|
|
29
|
+
{
|
|
30
|
+
"type": "label",
|
|
31
|
+
"content": "comment:",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "string",
|
|
35
|
+
"value": self.content,
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
42
|
+
write_u8(f, len(self.content))
|
|
43
|
+
f.write(self.content.encode("latin1"))
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import keyword
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import ControlRequirement
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
from tfbscript.reference import Reference
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@opcode("control")
|
|
13
|
+
@dataclass
|
|
14
|
+
class OpControl(Opcode):
|
|
15
|
+
target: Reference = field(default_factory=Reference)
|
|
16
|
+
script_control: ControlRequirement = field(default=ControlRequirement.Allow)
|
|
17
|
+
|
|
18
|
+
# Whether the payload carries the script_control byte. The byte is there
|
|
19
|
+
# for an actor target, but the target's type is the wrong thing to ask:
|
|
20
|
+
# resolving it only works while the read traversal is still inside the
|
|
21
|
+
# enclosing ops (see ParserContext.open_opcodes), so writing -- which
|
|
22
|
+
# happens outside any traversal -- got a different answer and dropped the
|
|
23
|
+
# byte. The payload length says it outright, there being only two shapes,
|
|
24
|
+
# 4 bytes for the reference alone and 5 with the byte. Measured over the
|
|
25
|
+
# corpus: 12,376 control ops, 965 of them 4 bytes and 11,411 of them 5,
|
|
26
|
+
# nothing else -- and the type test disagreed with the bytes 72 times.
|
|
27
|
+
_has_script_control: bool = True
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
@override
|
|
31
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpControl":
|
|
32
|
+
target = reader.readRef()
|
|
33
|
+
|
|
34
|
+
script_control = ControlRequirement.Allow
|
|
35
|
+
has_script_control = reader.size_remaining() >= 1
|
|
36
|
+
|
|
37
|
+
if has_script_control:
|
|
38
|
+
script_control = ControlRequirement(reader.read_u8())
|
|
39
|
+
|
|
40
|
+
return cls(
|
|
41
|
+
target=target,
|
|
42
|
+
script_control=script_control,
|
|
43
|
+
_has_script_control=has_script_control,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def source_line(self, inline: bool = False) -> str:
|
|
48
|
+
return f"{keyword('control (')} {self.target}, {self.script_control} {keyword(')')}"
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
52
|
+
self.target.write(f)
|
|
53
|
+
if self._has_script_control:
|
|
54
|
+
write_u8(f, self.script_control)
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
58
|
+
return {
|
|
59
|
+
"hasBody": True,
|
|
60
|
+
"fields": [
|
|
61
|
+
{
|
|
62
|
+
"type": "op-label",
|
|
63
|
+
"value": "control",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "ref",
|
|
67
|
+
"name": "target",
|
|
68
|
+
"ref": self.target,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "op-label",
|
|
72
|
+
"value": "script control: ",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"type": "enum",
|
|
76
|
+
"name": "script_control",
|
|
77
|
+
"entry": self.script_control,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import keyword, type_
|
|
5
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
6
|
+
from tfbscript.payload import PayloadReader
|
|
7
|
+
from tfbscript.reference import Reference
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@opcode("create variable")
|
|
11
|
+
@dataclass
|
|
12
|
+
class OpCreateVariable(Opcode):
|
|
13
|
+
variable: Reference = field(default_factory=Reference)
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
@override
|
|
17
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCreateVariable":
|
|
18
|
+
return cls(variable=reader.readRef())
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def source_line(self, inline: bool = False) -> str:
|
|
22
|
+
var_type = self.variable.entry.type if self.variable.entry else "unknown"
|
|
23
|
+
return f"{keyword('var')} {self.variable} : {type_(var_type)};"
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"fields": [
|
|
29
|
+
{
|
|
30
|
+
"type": "op-label",
|
|
31
|
+
"value": "create variable",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "ref",
|
|
35
|
+
"ref": self.variable,
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
42
|
+
self.variable.write(f)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import func_call
|
|
5
|
+
from tfbscript.binary import write_u8
|
|
6
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
7
|
+
from tfbscript.opcodes.enums import CutsceneCommand
|
|
8
|
+
from tfbscript.payload import PayloadReader
|
|
9
|
+
|
|
10
|
+
@opcode("cut-scene")
|
|
11
|
+
@dataclass
|
|
12
|
+
class OpCutScene(Opcode):
|
|
13
|
+
cutscene_command: CutsceneCommand = field(default=CutsceneCommand.Begin)
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
@override
|
|
17
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpCutScene":
|
|
18
|
+
cutscene_cmd = CutsceneCommand(reader.read_u8())
|
|
19
|
+
return cls(cutscene_command=cutscene_cmd)
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def source_line(self, inline: bool = False) -> str:
|
|
23
|
+
return func_call("cutScene", str(self.cutscene_command))
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
27
|
+
return {
|
|
28
|
+
"hasBody": False,
|
|
29
|
+
"fields": [
|
|
30
|
+
{
|
|
31
|
+
"type": "op-label",
|
|
32
|
+
"value": "cut-scene",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "enum",
|
|
36
|
+
"name": "cutscene_command",
|
|
37
|
+
"entry": self.cutscene_command,
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
44
|
+
write_u8(f, self.cutscene_command)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, BinaryIO, override
|
|
3
|
+
|
|
4
|
+
from tfbscript.ansi import operator
|
|
5
|
+
from tfbscript.opcodes.base import Opcode, opcode
|
|
6
|
+
from tfbscript.payload import PayloadReader
|
|
7
|
+
from tfbscript.reference import Reference
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@opcode("dec value")
|
|
11
|
+
@dataclass
|
|
12
|
+
class OpDecValue(Opcode):
|
|
13
|
+
lhs: Reference = field(default_factory=Reference)
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
@override
|
|
17
|
+
def parse_payload(cls, reader: PayloadReader) -> "OpDecValue":
|
|
18
|
+
return cls(lhs=reader.readRef())
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def source_line(self, inline: bool = False) -> str:
|
|
22
|
+
return f"{self.lhs} {operator('--')};"
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
def editor_repr(self) -> dict[str, Any]:
|
|
26
|
+
return {
|
|
27
|
+
"hasBody": False,
|
|
28
|
+
"fields": [
|
|
29
|
+
{
|
|
30
|
+
"type": "op-label",
|
|
31
|
+
"value": "decrement value",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "ref",
|
|
35
|
+
"name": "lhs",
|
|
36
|
+
"ref": self.lhs,
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def write_payload(self, f: BinaryIO) -> None:
|
|
43
|
+
self.lhs.write(f)
|