PyLibMS 4.2.4__py3-none-any.whl → 4.2.5__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.
- lms/common/field/io.py +17 -6
- lms/common/field/lms_datatype.py +4 -6
- lms/common/field/lms_field.py +7 -3
- lms/common/lms_constants.py +1 -1
- lms/common/stream/fileinfo.py +12 -3
- lms/common/stream/hashtable.py +6 -1
- lms/common/stream/section.py +7 -7
- lms/fileio/encoding.py +1 -1
- lms/fileio/io.py +1 -1
- lms/flowchart/definitions/flowchart.py +11 -6
- lms/flowchart/definitions/node.py +125 -91
- lms/flowchart/definitions/node_type.py +22 -9
- lms/flowchart/flw3.py +111 -47
- lms/flowchart/msbf.py +37 -10
- lms/flowchart/msbfio.py +30 -20
- lms/message/lms_messagetext.py +21 -13
- lms/message/msbt.py +27 -18
- lms/message/msbtentry.py +10 -10
- lms/message/msbtio.py +32 -18
- lms/message/section/atr1.py +7 -8
- lms/message/section/txt2.py +1 -1
- lms/message/tag/io/param_io.py +6 -6
- lms/message/tag/io/tag_io.py +17 -16
- lms/message/tag/lms_tag.py +26 -16
- lms/project/definitions/tag.py +19 -17
- lms/project/msbp.py +7 -7
- lms/project/msbpread.py +5 -1
- lms/titleconfig/config.py +16 -17
- lms/titleconfig/definitions/attribute.py +0 -1
- lms/titleconfig/definitions/nodes.py +60 -27
- lms/titleconfig/definitions/tags.py +4 -2
- lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +2 -1
- {pylibms-4.2.4.dist-info → pylibms-4.2.5.dist-info}/METADATA +17 -7
- {pylibms-4.2.4.dist-info → pylibms-4.2.5.dist-info}/RECORD +37 -37
- {pylibms-4.2.4.dist-info → pylibms-4.2.5.dist-info}/WHEEL +0 -0
- {pylibms-4.2.4.dist-info → pylibms-4.2.5.dist-info}/licenses/LICENSE +0 -0
- {pylibms-4.2.4.dist-info → pylibms-4.2.5.dist-info}/top_level.txt +0 -0
lms/common/field/io.py
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
|
-
from lms.common.field.lms_datatype import (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from lms.common.field.lms_datatype import (
|
|
4
|
+
LMS_DataType,
|
|
5
|
+
is_bool_datatype,
|
|
6
|
+
is_bytes_datatype,
|
|
7
|
+
is_list_datatype,
|
|
8
|
+
is_number_datatype,
|
|
9
|
+
)
|
|
6
10
|
from lms.common.field.lms_field import LMS_Field
|
|
7
11
|
from lms.fileio.io import FileReader, FileWriter
|
|
8
12
|
from lms.titleconfig.definitions.value import ValueDefinition
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
def read_field(
|
|
12
|
-
|
|
15
|
+
def read_field(
|
|
16
|
+
reader: FileReader,
|
|
17
|
+
definition: ValueDefinition,
|
|
18
|
+
read_variable_width_value: Callable | None = None,
|
|
19
|
+
) -> LMS_Field:
|
|
13
20
|
# String is excluded as their reading varies between MSBT/MSBF
|
|
14
21
|
match definition.datatype:
|
|
15
22
|
case LMS_DataType.UINT8:
|
|
@@ -43,7 +50,11 @@ def read_field(reader: FileReader, definition: ValueDefinition,
|
|
|
43
50
|
return LMS_Field(value, definition) # type: ignore
|
|
44
51
|
|
|
45
52
|
|
|
46
|
-
def write_field(
|
|
53
|
+
def write_field(
|
|
54
|
+
writer: FileWriter,
|
|
55
|
+
field: LMS_Field,
|
|
56
|
+
write_variable_width_value: Callable | None = None,
|
|
57
|
+
) -> None:
|
|
47
58
|
if is_number_datatype(field.value, field.datatype):
|
|
48
59
|
write_functions: dict[LMS_DataType, Callable] = {
|
|
49
60
|
LMS_DataType.UINT8: writer.write_uint8,
|
lms/common/field/lms_datatype.py
CHANGED
|
@@ -119,9 +119,7 @@ class LMS_DataType(Enum):
|
|
|
119
119
|
try:
|
|
120
120
|
return sizes[self]
|
|
121
121
|
except KeyError:
|
|
122
|
-
raise TypeError(
|
|
123
|
-
f"Stream size is not defined for '{self.to_string()}'."
|
|
124
|
-
)
|
|
122
|
+
raise TypeError(f"Stream size is not defined for '{self.to_string()}'.")
|
|
125
123
|
|
|
126
124
|
@classmethod
|
|
127
125
|
def from_string(cls, string: str):
|
|
@@ -138,8 +136,8 @@ class LMS_DataType(Enum):
|
|
|
138
136
|
|
|
139
137
|
|
|
140
138
|
def verify_number_from_datatype(
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
value: int | float,
|
|
140
|
+
datatype: LMS_DataType,
|
|
143
141
|
):
|
|
144
142
|
if datatype is LMS_DataType.FLOAT32:
|
|
145
143
|
max_value = FLOAT32_MAX
|
|
@@ -150,7 +148,7 @@ def verify_number_from_datatype(
|
|
|
150
148
|
max_value = 2 ** (bits - 1)
|
|
151
149
|
min_value = -max_value
|
|
152
150
|
else:
|
|
153
|
-
min_value, max_value = 0, (2
|
|
151
|
+
min_value, max_value = 0, (2**bits) - 1
|
|
154
152
|
|
|
155
153
|
if not min_value <= value <= max_value:
|
|
156
154
|
raise ValueError(
|
lms/common/field/lms_field.py
CHANGED
|
@@ -37,7 +37,9 @@ class LMS_FieldMap:
|
|
|
37
37
|
return {field.name: field.value for field in self.fields.values()}
|
|
38
38
|
|
|
39
39
|
@staticmethod
|
|
40
|
-
def create_default_dict_map(
|
|
40
|
+
def create_default_dict_map(
|
|
41
|
+
definitions: list[ValueDefinition],
|
|
42
|
+
) -> dict[str, FieldValue]:
|
|
41
43
|
map = {}
|
|
42
44
|
for definition in definitions:
|
|
43
45
|
match definition.datatype:
|
|
@@ -101,7 +103,9 @@ class LMS_Field:
|
|
|
101
103
|
"""
|
|
102
104
|
|
|
103
105
|
def __init__(
|
|
104
|
-
|
|
106
|
+
self,
|
|
107
|
+
value: int | str | float | bytes | bool,
|
|
108
|
+
definition: ValueDefinition,
|
|
105
109
|
):
|
|
106
110
|
_verify_value_from_definition(value, definition)
|
|
107
111
|
self._definition = definition
|
|
@@ -145,7 +149,7 @@ class LMS_Field:
|
|
|
145
149
|
|
|
146
150
|
|
|
147
151
|
def _verify_value_from_definition(
|
|
148
|
-
|
|
152
|
+
value: int | str | float | bytes | bool, definition: ValueDefinition
|
|
149
153
|
) -> None:
|
|
150
154
|
datatype = definition.datatype
|
|
151
155
|
|
lms/common/lms_constants.py
CHANGED
lms/common/stream/fileinfo.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
from lms.common import lms_exceptions
|
|
2
|
-
from lms.common.lms_constants import
|
|
2
|
+
from lms.common.lms_constants import (
|
|
3
|
+
BIG_ENDIAN_BOM,
|
|
4
|
+
SECTION_DATA_START,
|
|
5
|
+
LITTLE_ENDIAN_BOM,
|
|
6
|
+
SIZE_OFFSET,
|
|
7
|
+
)
|
|
3
8
|
from lms.common.lms_fileinfo import LMS_FileInfo
|
|
4
9
|
from lms.fileio.encoding import FileEncoding
|
|
5
10
|
from lms.fileio.io import FileReader, FileWriter
|
|
@@ -25,7 +30,9 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
25
30
|
version = reader.read_uint8()
|
|
26
31
|
|
|
27
32
|
if version < 3:
|
|
28
|
-
raise lms_exceptions.LMS_UnsupportedFileVersionError(
|
|
33
|
+
raise lms_exceptions.LMS_UnsupportedFileVersionError(
|
|
34
|
+
"Only version 3+ files are supported!"
|
|
35
|
+
)
|
|
29
36
|
|
|
30
37
|
section_count = reader.read_uint16()
|
|
31
38
|
|
|
@@ -34,7 +41,9 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
34
41
|
|
|
35
42
|
reader.seek(0, 2)
|
|
36
43
|
if file_size != (real_size := reader.tell()):
|
|
37
|
-
raise lms_exceptions.LMS_MisalignedSizeError(
|
|
44
|
+
raise lms_exceptions.LMS_MisalignedSizeError(
|
|
45
|
+
f"File size is misaligned!` Got {file_size} expected {real_size}."
|
|
46
|
+
)
|
|
38
47
|
|
|
39
48
|
reader.seek(SECTION_DATA_START)
|
|
40
49
|
|
lms/common/stream/hashtable.py
CHANGED
|
@@ -27,7 +27,12 @@ def read_labels(reader: FileReader) -> tuple[dict, int]:
|
|
|
27
27
|
return sorted_labels, slot_count
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def write_labels(
|
|
30
|
+
def write_labels(
|
|
31
|
+
writer: FileWriter,
|
|
32
|
+
labels: list[str],
|
|
33
|
+
slot_count: int,
|
|
34
|
+
index_map: dict[str, int] = None,
|
|
35
|
+
) -> None:
|
|
31
36
|
writer.write_uint32(slot_count)
|
|
32
37
|
|
|
33
38
|
hash_slots = {}
|
lms/common/stream/section.py
CHANGED
|
@@ -2,12 +2,12 @@ from typing import Any, Callable, Generator
|
|
|
2
2
|
|
|
3
3
|
from lms.fileio.io import FileReader, FileWriter
|
|
4
4
|
|
|
5
|
-
SECTION_PADDING_BYTE = b"\
|
|
5
|
+
SECTION_PADDING_BYTE = b"\xab"
|
|
6
6
|
SECTION_ALIGNMENT = 16
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def read_section_data(
|
|
10
|
-
|
|
10
|
+
reader: FileReader, section_count: int
|
|
11
11
|
) -> Generator[tuple[str, int], Any, None]:
|
|
12
12
|
reader.seek(0x20)
|
|
13
13
|
for _ in range(section_count):
|
|
@@ -24,11 +24,11 @@ def read_section_data(
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def write_section(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
writer: FileWriter,
|
|
28
|
+
magic: str,
|
|
29
|
+
section_call: Callable,
|
|
30
|
+
data: list[Any],
|
|
31
|
+
*write_arguments: Any,
|
|
32
32
|
) -> None:
|
|
33
33
|
writer.write_string(magic)
|
|
34
34
|
size_offset = writer.tell()
|
lms/fileio/encoding.py
CHANGED
|
@@ -10,7 +10,7 @@ class FileEncoding(IntEnum):
|
|
|
10
10
|
UTF32 = 0x02
|
|
11
11
|
|
|
12
12
|
def to_string_format(
|
|
13
|
-
|
|
13
|
+
self, is_big_endian: bool = False
|
|
14
14
|
) -> Literal["UTF-8", "UTF-16-BE", "UTF-16-LE", "UTF-32-BE", "UTF-32-LE"]:
|
|
15
15
|
"""Converts the FileEncoding to string format."""
|
|
16
16
|
match self:
|
lms/fileio/io.py
CHANGED
|
@@ -106,7 +106,7 @@ class FileReader:
|
|
|
106
106
|
def read_encoded_string(self):
|
|
107
107
|
message = b""
|
|
108
108
|
while (
|
|
109
|
-
|
|
109
|
+
raw_char := self.read_bytes(self.encoding.width)
|
|
110
110
|
) != self.encoding.terminator:
|
|
111
111
|
message += raw_char
|
|
112
112
|
return message.decode(self.encoding.to_string_format(self.is_big_endian))
|
|
@@ -7,7 +7,9 @@ from lms.flowchart.definitions.node import LMS_EntryNode, LMS_BranchNode, LMS_Ba
|
|
|
7
7
|
class LMS_Flowchart:
|
|
8
8
|
"""Class that represents a flowchart in a MSBF file."""
|
|
9
9
|
|
|
10
|
-
def __init__(
|
|
10
|
+
def __init__(
|
|
11
|
+
self, entry_point: LMS_EntryNode, id_generator: Callable[[], int]
|
|
12
|
+
) -> None:
|
|
11
13
|
self._entry_node = entry_point
|
|
12
14
|
self._nodes: dict[int, LMS_BaseNode] = {}
|
|
13
15
|
self._id_generator = id_generator
|
|
@@ -16,7 +18,6 @@ class LMS_Flowchart:
|
|
|
16
18
|
for node in entry_point.get_descendents():
|
|
17
19
|
self._nodes[node.id] = node
|
|
18
20
|
|
|
19
|
-
|
|
20
21
|
def __iter__(self) -> Iterator[LMS_BaseNode]:
|
|
21
22
|
return iter(self._nodes.values())
|
|
22
23
|
|
|
@@ -28,7 +29,7 @@ class LMS_Flowchart:
|
|
|
28
29
|
|
|
29
30
|
@property
|
|
30
31
|
def name(self) -> str:
|
|
31
|
-
"""The name of the flowchart.
|
|
32
|
+
"""The name of the flowchart."""
|
|
32
33
|
return self._entry_node.flowchart_name
|
|
33
34
|
|
|
34
35
|
@name.setter
|
|
@@ -41,7 +42,7 @@ class LMS_Flowchart:
|
|
|
41
42
|
return self._entry_node
|
|
42
43
|
|
|
43
44
|
@property
|
|
44
|
-
def nodes(self) -> MappingProxyType[LMS_BaseNode]:
|
|
45
|
+
def nodes(self) -> MappingProxyType[int, LMS_BaseNode]:
|
|
45
46
|
"""All nodes in the flowchart instance."""
|
|
46
47
|
return MappingProxyType(self._nodes)
|
|
47
48
|
|
|
@@ -52,10 +53,14 @@ class LMS_Flowchart:
|
|
|
52
53
|
:param node: the node to add.
|
|
53
54
|
"""
|
|
54
55
|
if not isinstance(node, LMS_BaseNode):
|
|
55
|
-
raise TypeError(
|
|
56
|
+
raise TypeError(
|
|
57
|
+
f"Node type '{type(node).__name__}' is not a child of LMS_BaseNode!"
|
|
58
|
+
)
|
|
56
59
|
|
|
57
60
|
if node in self._nodes.values():
|
|
58
|
-
raise ValueError(
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Node of ID '{node.id}' is already registered to this flowchart!"
|
|
63
|
+
)
|
|
59
64
|
|
|
60
65
|
node.id = self._id_generator()
|
|
61
66
|
self._nodes[node.id] = node
|
|
@@ -21,15 +21,17 @@ NO_NEXT_NODE = -1
|
|
|
21
21
|
|
|
22
22
|
class LMS_BaseNode:
|
|
23
23
|
"""
|
|
24
|
-
Class that represents common structure and methods for
|
|
24
|
+
Class that represents common structure and methods for any LMS_Node.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
def __init__(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
node_id: int,
|
|
30
|
+
node_type: LMS_NodeType,
|
|
31
|
+
parameter_type: LMS_NodeParameterType,
|
|
32
|
+
parameter_value: LMS_NodeParameter | None,
|
|
33
|
+
):
|
|
34
|
+
self.id = node_id
|
|
33
35
|
self._node_type = node_type
|
|
34
36
|
|
|
35
37
|
self._parameter_type = parameter_type
|
|
@@ -68,13 +70,19 @@ class LMS_BaseNode:
|
|
|
68
70
|
@parameter_value.setter
|
|
69
71
|
def parameter_value(self, value: LMS_NodeParameter):
|
|
70
72
|
if self._parameter_type == LMS_NodeParameterType.NONE:
|
|
71
|
-
raise TypeError(
|
|
73
|
+
raise TypeError(
|
|
74
|
+
"Unable to set parameter values when there is no parameter!"
|
|
75
|
+
)
|
|
72
76
|
|
|
73
77
|
if isinstance(self._parameter_value, LMS_FieldMap):
|
|
74
|
-
raise ValueError(
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"Edit a parameter value through the value property of a field!"
|
|
80
|
+
)
|
|
75
81
|
|
|
76
82
|
if not isinstance(value, self._parameter_type.builtin_type):
|
|
77
|
-
raise TypeError(
|
|
83
|
+
raise TypeError(
|
|
84
|
+
f"Wrong value provided! Expected {self._parameter_type.builtin_type} got {type(value)}"
|
|
85
|
+
)
|
|
78
86
|
|
|
79
87
|
match self._parameter_type:
|
|
80
88
|
case LMS_NodeParameterType.STRING:
|
|
@@ -84,15 +92,19 @@ class LMS_BaseNode:
|
|
|
84
92
|
self._parameter_value = value
|
|
85
93
|
case _:
|
|
86
94
|
for i, parameter in enumerate(value):
|
|
87
|
-
verify_number_from_datatype(
|
|
88
|
-
|
|
95
|
+
verify_number_from_datatype(
|
|
96
|
+
parameter,
|
|
97
|
+
self._parameter_type.sliced_datatype[self._parameter_type][i],
|
|
98
|
+
)
|
|
89
99
|
|
|
90
100
|
self._parameter_value = value
|
|
91
101
|
|
|
92
102
|
def set_next_node(self, node: LMS_BaseNode | None) -> LMS_BaseNode | None:
|
|
93
|
-
"""
|
|
103
|
+
"""
|
|
104
|
+
Set the next node for the instance.
|
|
94
105
|
|
|
95
|
-
:param node: the node object, None for no next node.
|
|
106
|
+
:param node: the node object, None for no next node.
|
|
107
|
+
"""
|
|
96
108
|
self._next_node = node
|
|
97
109
|
return node
|
|
98
110
|
|
|
@@ -129,17 +141,18 @@ class LMS_BaseNode:
|
|
|
129
141
|
|
|
130
142
|
class LMS_MessageNode(LMS_BaseNode):
|
|
131
143
|
"""
|
|
132
|
-
Class that represents a message node. Node utilized in
|
|
144
|
+
Class that represents a message node. Node utilized in junction with MSBT entries.
|
|
133
145
|
"""
|
|
134
146
|
|
|
135
|
-
def __init__(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
node_id: int,
|
|
150
|
+
msbt_index: int,
|
|
151
|
+
label_index: int,
|
|
152
|
+
msbt: MSBT | None = None,
|
|
153
|
+
):
|
|
141
154
|
super().__init__(
|
|
142
|
-
|
|
155
|
+
node_id,
|
|
143
156
|
LMS_NodeType.MESSAGE,
|
|
144
157
|
LMS_NodeParameterType.NONE,
|
|
145
158
|
None,
|
|
@@ -189,14 +202,16 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
189
202
|
Class that represents a branch node.
|
|
190
203
|
"""
|
|
191
204
|
|
|
192
|
-
def __init__(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
node_id,
|
|
208
|
+
parameter_type: LMS_NodeParameterType,
|
|
209
|
+
parameter_value: LMS_NodeParameter,
|
|
210
|
+
condition_id: int,
|
|
211
|
+
definition: NodeDefinition | None = None,
|
|
212
|
+
):
|
|
198
213
|
super().__init__(
|
|
199
|
-
|
|
214
|
+
node_id,
|
|
200
215
|
LMS_NodeType.BRANCH,
|
|
201
216
|
parameter_type,
|
|
202
217
|
parameter_value,
|
|
@@ -212,14 +227,17 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
212
227
|
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
213
228
|
|
|
214
229
|
def set_next_node(self, node: LMS_BaseNode | None) -> LMS_BaseNode | None:
|
|
215
|
-
raise NotImplementedError(
|
|
230
|
+
raise NotImplementedError(
|
|
231
|
+
"Use function set_branch_case to alter the flow of a branch node!"
|
|
232
|
+
)
|
|
216
233
|
|
|
217
234
|
@classmethod
|
|
218
|
-
def new(
|
|
235
|
+
def new(
|
|
236
|
+
cls,
|
|
219
237
|
parameter_type: LMS_NodeParameterType,
|
|
220
238
|
parameter_value: LMS_NodeParameter,
|
|
221
239
|
condition_id: int,
|
|
222
|
-
|
|
240
|
+
):
|
|
223
241
|
"""
|
|
224
242
|
Instantiates a new branch node.
|
|
225
243
|
|
|
@@ -230,18 +248,23 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
230
248
|
return cls(None, parameter_type, parameter_value, condition_id)
|
|
231
249
|
|
|
232
250
|
@classmethod
|
|
233
|
-
def new_from_definition(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
def new_from_definition(
|
|
252
|
+
cls,
|
|
253
|
+
definition: NodeDefinition,
|
|
254
|
+
**parameter_values: str | bool | float | bytes,
|
|
255
|
+
):
|
|
237
256
|
"""
|
|
238
257
|
Instantiates a new branch node from a definition.
|
|
239
258
|
|
|
240
259
|
:param definition: the node definition.
|
|
241
260
|
:param parameter_values: keyword arguments of parameters.
|
|
242
261
|
"""
|
|
243
|
-
converted = LMS_FieldMap.from_dict(
|
|
244
|
-
|
|
262
|
+
converted = LMS_FieldMap.from_dict(
|
|
263
|
+
parameter_values, definition.parameter_definitions
|
|
264
|
+
)
|
|
265
|
+
return cls(
|
|
266
|
+
None, definition.parameter_type, converted, definition.id, definition
|
|
267
|
+
)
|
|
245
268
|
|
|
246
269
|
@property
|
|
247
270
|
def definition(self) -> NodeDefinition | None:
|
|
@@ -252,7 +275,9 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
252
275
|
def name(self) -> str:
|
|
253
276
|
"""Returns the name of the node. Must have provided a node configuration when reading the file."""
|
|
254
277
|
if self._definition is None:
|
|
255
|
-
raise LMS_Error(
|
|
278
|
+
raise LMS_Error(
|
|
279
|
+
"Unable to access the name of the node without a definition set!"
|
|
280
|
+
)
|
|
256
281
|
|
|
257
282
|
return self._definition.name
|
|
258
283
|
|
|
@@ -271,12 +296,13 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
271
296
|
"""The node branches."""
|
|
272
297
|
return MappingProxyType(self._branches)
|
|
273
298
|
|
|
274
|
-
def get_case_options(
|
|
275
|
-
|
|
299
|
+
def get_case_options(
|
|
300
|
+
self, compress_shared_references: bool = False
|
|
301
|
+
) -> list[tuple[tuple[int, ...], str, LMS_BaseNode | None]]:
|
|
276
302
|
"""
|
|
277
303
|
Retrieves the cases from this node formatted with the proper messages.
|
|
278
304
|
|
|
279
|
-
If a definition is provided, and either ``case_format`` or ``case_options`` are
|
|
305
|
+
If a definition is provided, and either ``case_format`` or ``case_options`` are provided, the resulting
|
|
280
306
|
message will utilize those values to format the message.
|
|
281
307
|
|
|
282
308
|
:param compress_shared_references: whether to compress shared references into one case.
|
|
@@ -287,10 +313,11 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
287
313
|
case_map.setdefault(branch, []).append(case)
|
|
288
314
|
|
|
289
315
|
if self.definition is not None:
|
|
290
|
-
|
|
291
316
|
if self.definition.case_options:
|
|
292
317
|
for case, branch in self._branches.items():
|
|
293
|
-
case_message = self.definition.case_options.get(
|
|
318
|
+
case_message = self.definition.case_options.get(
|
|
319
|
+
case, f"Case {case}"
|
|
320
|
+
)
|
|
294
321
|
|
|
295
322
|
result.append(((case,), case_message, branch))
|
|
296
323
|
|
|
@@ -299,7 +326,9 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
299
326
|
if self.definition.case_format is not None:
|
|
300
327
|
if compress_shared_references:
|
|
301
328
|
for branch, cases in case_map.items():
|
|
302
|
-
case_message = self._format_case_message(
|
|
329
|
+
case_message = self._format_case_message(
|
|
330
|
+
merge_shared_cases(cases)
|
|
331
|
+
)
|
|
303
332
|
result.append((tuple(cases), case_message, branch))
|
|
304
333
|
else:
|
|
305
334
|
for case, branch in self._branches.items():
|
|
@@ -310,7 +339,11 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
310
339
|
|
|
311
340
|
if compress_shared_references:
|
|
312
341
|
for branch, cases in case_map.items():
|
|
313
|
-
case_message =
|
|
342
|
+
case_message = (
|
|
343
|
+
f"Case {cases[0]}"
|
|
344
|
+
if len(cases) == 1
|
|
345
|
+
else f"Cases {merge_shared_cases(cases)}"
|
|
346
|
+
)
|
|
314
347
|
result.append((tuple(cases), case_message, branch))
|
|
315
348
|
else:
|
|
316
349
|
for case, branch in self._branches.items():
|
|
@@ -327,7 +360,8 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
327
360
|
string_map = {}
|
|
328
361
|
for param_definition in self.definition.parameter_definitions:
|
|
329
362
|
string_map[param_definition.name] = self.parameter_value[
|
|
330
|
-
param_definition.name
|
|
363
|
+
param_definition.name
|
|
364
|
+
].value
|
|
331
365
|
|
|
332
366
|
string_map["case"] = case_prefix
|
|
333
367
|
return prefix.substitute(**string_map)
|
|
@@ -397,18 +431,15 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
397
431
|
Class that represents an event node. Node utilized for in-game actions.
|
|
398
432
|
"""
|
|
399
433
|
|
|
400
|
-
def __init__(
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
LMS_NodeType.EVENT,
|
|
410
|
-
parameter_type,
|
|
411
|
-
parameter_value)
|
|
434
|
+
def __init__(
|
|
435
|
+
self,
|
|
436
|
+
node_id: int,
|
|
437
|
+
parameter_type: LMS_NodeParameterType,
|
|
438
|
+
parameter_value: LMS_FieldMap | int | str | tuple[int, ...],
|
|
439
|
+
event_id: int,
|
|
440
|
+
definition: NodeDefinition | None = None,
|
|
441
|
+
):
|
|
442
|
+
super().__init__(node_id, LMS_NodeType.EVENT, parameter_type, parameter_value)
|
|
412
443
|
|
|
413
444
|
self._event_id = event_id
|
|
414
445
|
self._definition = definition
|
|
@@ -419,7 +450,12 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
419
450
|
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
420
451
|
|
|
421
452
|
@classmethod
|
|
422
|
-
def new(
|
|
453
|
+
def new(
|
|
454
|
+
cls,
|
|
455
|
+
parameter_type: LMS_NodeParameterType,
|
|
456
|
+
parameter_value: int | str | tuple[int, ...],
|
|
457
|
+
event_id: int,
|
|
458
|
+
):
|
|
423
459
|
"""
|
|
424
460
|
Instantiates a new event node.
|
|
425
461
|
|
|
@@ -432,18 +468,23 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
432
468
|
return cls(None, parameter_type, parameter_value, event_id)
|
|
433
469
|
|
|
434
470
|
@classmethod
|
|
435
|
-
def new_from_definition(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
471
|
+
def new_from_definition(
|
|
472
|
+
cls,
|
|
473
|
+
definition: NodeDefinition,
|
|
474
|
+
**parameter_values: str | int | str | tuple[int, ...],
|
|
475
|
+
):
|
|
439
476
|
"""
|
|
440
477
|
Instantiates a new event node from a definition.
|
|
441
478
|
|
|
442
479
|
:param definition: the node definition.
|
|
443
480
|
:param parameter_values: keyword arguments of parameters.
|
|
444
481
|
"""
|
|
445
|
-
converted = LMS_FieldMap.from_dict(
|
|
446
|
-
|
|
482
|
+
converted = LMS_FieldMap.from_dict(
|
|
483
|
+
parameter_values, definition.parameter_definitions
|
|
484
|
+
)
|
|
485
|
+
return cls(
|
|
486
|
+
None, definition.parameter_type, converted, definition.id, definition
|
|
487
|
+
)
|
|
447
488
|
|
|
448
489
|
@property
|
|
449
490
|
def definition(self) -> NodeDefinition | None:
|
|
@@ -469,12 +510,8 @@ class LMS_EntryNode(LMS_BaseNode):
|
|
|
469
510
|
Class that represents an entry node. Node that is at the start of a flowchart.
|
|
470
511
|
"""
|
|
471
512
|
|
|
472
|
-
def __init__(self,
|
|
473
|
-
super().__init__(
|
|
474
|
-
id,
|
|
475
|
-
LMS_NodeType.ENTRY,
|
|
476
|
-
LMS_NodeParameterType.NONE,
|
|
477
|
-
None)
|
|
513
|
+
def __init__(self, node_id: int, flowchart_name: str = ""):
|
|
514
|
+
super().__init__(node_id, LMS_NodeType.ENTRY, LMS_NodeParameterType.NONE, None)
|
|
478
515
|
|
|
479
516
|
self.flowchart_name = flowchart_name
|
|
480
517
|
|
|
@@ -484,14 +521,10 @@ class LMS_JumpNode(LMS_BaseNode):
|
|
|
484
521
|
Class that represents a jump node. Node that jumps to another flowchart.
|
|
485
522
|
"""
|
|
486
523
|
|
|
487
|
-
def __init__(self,
|
|
488
|
-
super().__init__(
|
|
489
|
-
id,
|
|
490
|
-
LMS_NodeType.JUMP,
|
|
491
|
-
LMS_NodeParameterType.NONE,
|
|
492
|
-
None)
|
|
524
|
+
def __init__(self, node_id: int, unknown_short0a: int, next_entry_point: LMS_EntryNode = None):
|
|
525
|
+
super().__init__(node_id, LMS_NodeType.JUMP, LMS_NodeParameterType.NONE, None)
|
|
493
526
|
|
|
494
|
-
self.next_flowchart
|
|
527
|
+
self.next_flowchart = next_entry_point
|
|
495
528
|
|
|
496
529
|
# TODO: Document this unknown value at 0xA in the node
|
|
497
530
|
# This value is usually -1 in TL 3DS but is a set value in other games
|
|
@@ -505,17 +538,12 @@ class LMS_JumpNode(LMS_BaseNode):
|
|
|
505
538
|
:param next_entry_point: the next entry point.
|
|
506
539
|
:param unknown_short0a: unknown short value. -1 if there is no value.
|
|
507
540
|
"""
|
|
508
|
-
|
|
509
|
-
node.next_flowchart = next_entry_point
|
|
510
|
-
return node
|
|
541
|
+
return cls(None, unknown_short0a, next_entry_point)
|
|
511
542
|
|
|
512
543
|
@property
|
|
513
|
-
def next_flowchart_id(self) -> int:
|
|
544
|
+
def next_flowchart_id(self) -> int | None:
|
|
514
545
|
"""The ID of the next flowchart."""
|
|
515
|
-
if self.next_flowchart is
|
|
516
|
-
return self.next_flowchart.id
|
|
517
|
-
|
|
518
|
-
return self._stream_next_id
|
|
546
|
+
return None if self.next_flowchart is None else self.next_flowchart.id
|
|
519
547
|
|
|
520
548
|
def set_next_node(self, node: LMS_BaseNode | None) -> LMS_BaseNode | None:
|
|
521
549
|
super().set_next_node(node)
|
|
@@ -524,18 +552,24 @@ class LMS_JumpNode(LMS_BaseNode):
|
|
|
524
552
|
return node
|
|
525
553
|
|
|
526
554
|
|
|
527
|
-
def verify_parameter_structure(
|
|
555
|
+
def verify_parameter_structure(
|
|
556
|
+
value: int | tuple[int, ...] | str, parameter_type: LMS_NodeParameterType
|
|
557
|
+
) -> None:
|
|
528
558
|
if parameter_type is LMS_NodeParameterType.NONE:
|
|
529
|
-
raise node_exceptions.LMS_NodeInvalidParameterTypeError(
|
|
559
|
+
raise node_exceptions.LMS_NodeInvalidParameterTypeError(
|
|
560
|
+
"There must be a parameter type!"
|
|
561
|
+
)
|
|
530
562
|
|
|
531
563
|
if not isinstance(value, parameter_type.builtin_type):
|
|
532
564
|
raise node_exceptions.LMS_NodeInvalidParameterValueError(
|
|
533
565
|
f"Parameter type {parameter_type} expects type {parameter_type.builtin_type},"
|
|
534
|
-
f" but got {type(value)}."
|
|
566
|
+
f" but got {type(value)}."
|
|
567
|
+
)
|
|
535
568
|
|
|
536
569
|
if isinstance(value, tuple) and len(value) != parameter_type.value_count:
|
|
537
570
|
raise node_exceptions.LMS_NodeMissingParameterValueError(
|
|
538
|
-
f"Parameter type {parameter_type} expects {parameter_type.value_count} parameter values, got {len(value)}!"
|
|
571
|
+
f"Parameter type {parameter_type} expects {parameter_type.value_count} parameter values, got {len(value)}!"
|
|
572
|
+
)
|
|
539
573
|
|
|
540
574
|
|
|
541
575
|
def merge_shared_cases(cases: list[int]) -> str:
|