def from_string(cls, data: Union[bytes, str]) -> Editor: if isinstance(data, bytes): try: data = data.decode() except UnicodeDecodeError: raise EditorError("Invalid level data received.") from None if not data: # nothing interesting... return cls() info, *objects = data.split(";") # remove last object if none try: last = objects.pop() if last: objects.append(last) except IndexError: pass header = Header.from_string(info) objects = list(map(Object.from_string, objects)) return cls(*objects).set_header(header)
def __init__(self, *objects: Sequence[Object], **header_args) -> None: self.header = Header(**header_args) self.objects = list(objects) self._set_callback()
class Editor: """Editor interface of gd.py. Editor can be created either by hand, from decoded level's data, or taken from a level itself. """ def __init__(self, *objects: Sequence[Object], **header_args) -> None: self.header = Header(**header_args) self.objects = list(objects) self._set_callback() def __json__(self) -> Dict[str, Union[Header, Sequence[Object]]]: return dict(header=self.header, objects=self.objects) def _set_callback(self, caller: Any = None, attribute: Optional[str] = None) -> None: self._callback = caller self._attr = attribute @classmethod def launch(cls, caller: Any, attribute: str) -> Editor: return launch_editor(caller, attribute) def dump_back(self) -> None: dump_editor(self) @classmethod def from_string(cls, data: Union[bytes, str]) -> Editor: if isinstance(data, bytes): try: data = data.decode() except UnicodeDecodeError: raise EditorError("Invalid level data received.") from None if not data: # nothing interesting... return cls() info, *objects = data.split(";") # remove last object if none try: last = objects.pop() if last: objects.append(last) except IndexError: pass header = Header.from_string(info) objects = list(map(Object.from_string, objects)) return cls(*objects).set_header(header) def __repr__(self) -> str: info = {"objects": len(self.objects), "header": "<...>"} return make_repr(self, info) def __iter__(self) -> Iterable[Object]: return iter(self.objects) def __len__(self) -> int: return len(self.objects) def set_header(self, header: Header) -> Editor: """Set header of Editor instance to ``header``.""" if isinstance(header, Header): self.header = header return self def get_header(self) -> Header: """Get header of Editor instance.""" return self.header def copy_header(self) -> Header: """Copy header of Editor instance.""" return self.header.copy() def get_groups(self) -> Iterable[int]: """Fetch all used groups in Editor instance and return them as a set.""" groups = set() for obj in self.objects: new_groups, group = obj.groups, obj.target_group if new_groups is not None: groups.update(new_groups) if group is not None: groups.add(group) return groups def get_color_ids(self) -> Iterable[int]: """Fetch all used color IDs in Editor instance and return them as a set.""" color_ids = set() for obj in self.objects: color_1, color_2 = obj.color_1, obj.color_2 if color_1 is not None: color_ids.add(color_1) if color_2 is not None: color_ids.add(color_2) return color_ids def get_free_group(self) -> Optional[int]: """Get next free group of Editor instance. ``None`` if not found.""" return _find_next(self.get_groups()) def get_free_color_id(self) -> Optional[int]: """Get next free color ID of Editor instance. ``None`` if not found.""" return _find_next(self.get_color_ids()) def get_portals(self) -> List[Object]: """Fetch all portals / speed triggers used in this level, sorted by position in level.""" return sorted(filter(_is_portal, self.objects), key=(_get_x)) def get_speed_portals(self) -> List[Object]: """Fetch all speed triggers used in this level, sorted by position in level.""" return sorted(filter(_is_speed_portal, self.objects), key=(_get_x)) def get_x_length(self) -> Union[float, int]: """Get the X position of a last object. Default is 0.""" return max(map(_get_x, self.objects), default=0) def get_speed(self) -> Speed: """Get speed from a header, or return normal speed.""" return self.header.speed or Speed(0) def get_length(self, x: Optional[Union[float, int]] = None) -> float: """Calculate length of the level in seconds.""" if x is None: x = self.get_x_length() portals = self.get_speed_portals() speed = self.get_speed() return get_length_from_x(x, speed, portals) def get_color(self, directive_or_id: Union[int, str]) -> Optional[ColorChannel]: """Get color by ID or special directive. ``None`` if not found.""" return self.header.colors.get(directive_or_id) def get_colors(self) -> ColorCollection: """Return a reference to colors of the Editor instance.""" return self.header.colors def copy_colors(self) -> ColorCollection: """Copy colors of the Editor instance.""" return self.header.copy_colors() def add_colors( self, *colors: Sequence[Union[Tuple[int, int, int], int, ColorCollection]] ) -> Editor: """Add colors to the Editor.""" self.header.colors.update(colors) return self def get_objects(self) -> List[Object]: """Return a reference to object of the Editor instance.""" return self.objects def add_objects(self, *objects: Sequence[Object]) -> Editor: """Add objects to ``self.objects``.""" self.objects.extend(list(objects)) return self def copy_objects(self) -> List[Object]: """Copy objects of the Editor instance.""" return list(obj.copy() for obj in self.objects) def map(self, function: Callable[[Object], Any]) -> map: """:class:`map`: Same as calling ``map`` on ``self.objects``.""" return map(function, self.objects) def filter(self, function: Callable[[Object], Any]) -> filter: """:class:`filter`: Same as calling ``filter`` on ``self.objects``.""" return filter(function, self.objects) def dump_to_level(self, level: Level, append_sc: bool = True) -> None: """Dump ``self`` to a ``level`` object.""" level.options.update(data=self.dump(append_sc=append_sc)) def dump_to_api(self, api: LevelAPI, append_sc: bool = True) -> None: api.edit(level_string=self.dump(append_sc=append_sc)) def dump(self, append_sc: bool = True) -> str: """Dump all objects and header into a level data string.""" seq = [self.header.dump(), *(obj.dump() for obj in self.objects)] if append_sc: seq.append("") data = ";".join(map(str, seq)) return data def copy(self) -> Editor: """Return a copy of the Editor instance.""" return Editor(self.copy_header(), *self.copy_objects())