Skip to content

Editor

Editor

Bases: Sequence[Object], RobTop

Represents editors.

Source code in gd/api/editor.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
@define()
class Editor(Sequence[Object], RobTop):
    """Represents editors."""

    header: Header = field(factory=Header)
    """The header of the editor."""

    objects: List[Object] = field(factory=list)
    """The objects of the editor."""

    @classmethod
    def from_objects(cls, *objects: Object, header: Header) -> Self:
        return cls(header, list(objects))

    @classmethod
    def from_object_iterable(cls, objects: Iterable[Object], header: Header) -> Self:
        return cls(header, list(objects))

    def __len__(self) -> int:
        return len(self.objects)

    @overload
    def __getitem__(self, index: int) -> Object:
        ...

    @overload
    def __getitem__(self, index: slice) -> Self:
        ...

    def __getitem__(self, index: Union[int, slice]) -> Union[Object, Self]:
        if is_slice(index):
            return self.from_object_iterable(self.objects[index], self.header)

        return self.objects[index]  # type: ignore

    @property
    def color_channels(self) -> ColorChannels:
        return self.header.color_channels

    def set_header(self, header: Header) -> Self:
        self.header = header

        return self

    def set_color_channels(self, color_channels: ColorChannels) -> Self:
        self.header.color_channels = color_channels

        return self

    @wrap_iter
    def iter_group_ids(self) -> Iterator[int]:
        for object in self.objects:
            yield from object.group_ids

            if has_target_group(object):
                yield object.target_group_id

            if has_additional_group(object):
                yield object.additional_group_id

    @property
    def group_ids(self) -> Set[int]:
        return self.iter_group_ids().set()

    @property
    def free_group_id(self) -> int:
        return find_next(self.group_ids)

    @wrap_iter
    def iter_color_channel_ids(self) -> Iterator[int]:
        for object in self.objects:
            yield object.base_color_channel_id
            yield object.detail_color_channel_id

        yield from self.color_channels

    @property
    def color_channel_ids(self) -> Set[int]:
        return self.iter_color_channel_ids().set()

    @property
    def free_color_channel_id(self) -> int:
        return find_next(self.color_channel_ids)

    @wrap_iter
    def iter_start_positions(self) -> Iterator[StartPosition]:
        return filter(is_start_position, self.objects)

    @property
    def start_position(self) -> List[StartPosition]:
        return self.iter_start_positions().sorted_by(get_x)

    @wrap_iter
    def iter_portals(self) -> Iterator[Object]:
        return (object for object in self.objects if object.is_portal())

    @property
    def portals(self) -> List[Object]:
        return self.iter_portals().sorted_by(get_x)

    @wrap_iter
    def iter_speed_changes(self) -> Iterator[Object]:
        return (object for object in self.objects if object.is_speed_change())

    @property
    def speed_changes(self) -> List[Object]:
        return self.iter_speed_changes().sorted_by(get_x)

    @wrap_iter
    def iter_triggers(self) -> Iterator[Trigger]:
        return filter(is_trigger, self.objects)

    @property
    def triggers(self) -> List[Trigger]:
        return self.iter_triggers().sorted_by(get_x)

    @property
    def x_length(self) -> float:
        return iter(self.objects).map(get_x).max().unwrap_or(DEFAULT_X)

    @property
    def start_speed(self) -> Speed:
        return self.header.speed

    @property
    def length(self) -> float:
        return time_length(self.x_length, self.start_speed, self.speed_changes)

    @classmethod
    def from_robtop(cls, string: str) -> Self:
        iterator = iter(split_objects(string)).filter(None)

        header = iterator.next().map(Header.from_robtop).unwrap_or_else(Header)

        objects = iterator.map(object_from_robtop).collect_iter(migrate_objects).list()

        return cls(header, objects)

    def to_robtop(self) -> str:
        return (
            iter(self.objects)
            .map(object_to_robtop)
            .prepend(self.header.to_robtop())
            .collect(concat_objects)
        )

    @classmethod
    def can_be_in(cls, string: str) -> bool:
        return OBJECTS_SEPARATOR in string

header: Header = field(factory=Header) class-attribute instance-attribute

The header of the editor.

objects: List[Object] = field(factory=list) class-attribute instance-attribute

The objects of the editor.

time_length(distance: float, start_speed: Speed = Speed.NORMAL, speed_changes: Iterable[Object] = ()) -> float

Computes the time (in seconds) to travel from 0 to distance, respecting speed portals.

Parameters:

Name Type Description Default
distance float

The distance to stop calculating at.

required
start_speed Speed

The starting speed (found in the header).

NORMAL
speed_changes Iterable[Object]

Speed changes in the level, ordered by x position.

()

Returns:

Type Description
float

The calculated time (in seconds).

Source code in gd/api/editor.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def time_length(
    distance: float,
    start_speed: Speed = Speed.NORMAL,
    speed_changes: Iterable[Object] = (),
) -> float:
    """Computes the time (in seconds) to travel from `0` to `distance`, respecting speed portals.

    Arguments:
        distance: The distance to stop calculating at.
        start_speed: The starting speed (found in the header).
        speed_changes: Speed changes in the level, ordered by `x` position.

    Returns:
        The calculated time (in seconds).
    """
    magic = SPEED_TO_MAGIC[start_speed]

    if not speed_changes:
        return distance / magic

    last_x = 0.0
    total = 0.0

    for speed_change in speed_changes:
        x = speed_change.x

        if x > distance:
            break

        delta = x - last_x

        total += delta / magic

        magic = SPEED_CHANGE_TO_MAGIC[SpeedChangeType(speed_change.id)]

        last_x = x

    delta = distance - last_x

    total += delta / magic

    return total