diff --git a/lihzahrd/errors.py b/lihzahrd/errors.py new file mode 100644 index 0000000..16be521 --- /dev/null +++ b/lihzahrd/errors.py @@ -0,0 +1,2 @@ +class InvalidFooterError(Exception): + """This exception is raised if the footer contents do not match the expected data.""" diff --git a/lihzahrd/tiles/__init__.py b/lihzahrd/tiles/__init__.py index 6d6484a..2ca8f29 100644 --- a/lihzahrd/tiles/__init__.py +++ b/lihzahrd/tiles/__init__.py @@ -9,6 +9,7 @@ from .block import Block from .wall import Wall from .liquid import Liquid from .tile import Tile +from .tilematrix import TileMatrix __all__ = ["LiquidType", "RLEEncoding", "Shape", "Wiring", "BlockType", "FrameImportantData", "WallType", "Block", - "Wall", "Liquid", "Tile"] + "Wall", "Liquid", "Tile", "TileMatrix"] diff --git a/lihzahrd/tiles/tilematrix.py b/lihzahrd/tiles/tilematrix.py new file mode 100644 index 0000000..4493949 --- /dev/null +++ b/lihzahrd/tiles/tilematrix.py @@ -0,0 +1,61 @@ +import typing +from .tile import Tile +from ..fileutils import FileReader, Coordinates + + +class TileMatrix: + """A huge matrix containing the tiles of a whole world.""" + + def __init__(self): + self._tiles: typing.List[typing.List[Tile]] = [] + + def __repr__(self): + if len(self._tiles) > 0: + return f"" + return f"" + + def __getitem__(self, item: typing.Union[typing.Tuple, Coordinates]): + """Get a tile at specific coordinates. + + (x=0, y=0) returns the top-left tile in the map. + + You can specify a negative coordinate to count tiles respectively from the right or from the bottom: + (x=-1, y=-1) returns the bottom-right tile in the map.""" + if isinstance(item, Coordinates): + return self._tiles[item.x][item.y] + elif isinstance(item, tuple): + return self._tiles[item[0]][item[1]] + else: + raise TypeError(f"Unsupported type: {item.__class__.__name__}") + + def __setitem__(self, key: typing.Union[typing.Tuple, Coordinates], value: Tile): + """Change a tile at specific coordinates. + + The same properties that apply to __getitem__ are valid for __setitem__.""" + if not isinstance(value, Tile): + raise TypeError(f"Invalid type: {value.__class__.__name__} instead of Tile") + if isinstance(key, Coordinates): + self._tiles[key.x][key.y] = value + elif isinstance(key, tuple): + self._tiles[key[0]][key[1]] = value + else: + raise TypeError(f"Invalid type: {key.__class__.__name__} instead of tuple or Coordinates") + + def __len__(self): + """Return the amount of tiles present in the matrix.""" + if len(self._tiles) > 0: + return len(self._tiles) * len(self._tiles[0]) + return 0 + + def add_column(self, column: typing.List[Tile]): + """Add a new column to the matrix.""" + if len(self._tiles) > 0 and len(column) != len(self._tiles[0]): + raise ValueError("column has a different length than the others in the matrix") + self._tiles.append(column) + + @property + def size(self) -> Coordinates: + """Return the size of the matrix as a pair of coordinates.""" + if len(self._tiles) > 0: + return Coordinates(len(self._tiles), len(self._tiles[0])) + return Coordinates(0, 0) diff --git a/lihzahrd/tiles/wiring.py b/lihzahrd/tiles/wiring.py index feb338b..950c30d 100644 --- a/lihzahrd/tiles/wiring.py +++ b/lihzahrd/tiles/wiring.py @@ -37,4 +37,4 @@ class Wiring: if flags3 is not None: return cls(red=flags2[1], green=flags2[2], blue=flags2[3], yellow=flags3[5], actuator=flags3[1]) return cls(red=flags2[1], green=flags2[2], blue=flags2[3]) - return None + return cls() diff --git a/lihzahrd/timer.py b/lihzahrd/timer.py index 42843e8..36861db 100644 --- a/lihzahrd/timer.py +++ b/lihzahrd/timer.py @@ -3,7 +3,8 @@ import typing class Timer: - def __init__(self, name: str, display: bool = False): + """An object to track and print the time required to perform a section of code.""" + def __init__(self, name: str, display: bool = True): self.name: str = name self.display: bool = display self._start_time: typing.Optional[float] = None diff --git a/lihzahrd/world.py b/lihzahrd/world.py index 73afdc3..fd1403e 100644 --- a/lihzahrd/world.py +++ b/lihzahrd/world.py @@ -11,6 +11,7 @@ from .tileentities import * from .pressureplates import * from .townmanager import * from .timer import Timer +from .errors import InvalidFooterError class World: @@ -46,7 +47,7 @@ class World: anglers_quest: AnglerQuest, clouds: Clouds, cultist_delay: int, - tiles: typing.List[typing.List[Tile]], + tiles: TileMatrix, chests: typing.List[Chest], signs: typing.List[Sign], npcs: typing.List[NPC], @@ -145,7 +146,7 @@ class World: self.anglers_quest: AnglerQuest = anglers_quest """Information about today's Angler's Quest.""" - self.tiles: typing.List[typing.List[Tile]] = tiles + self.tiles: TileMatrix = tiles """A matrix of all the tiles present in the world.""" self.chests: typing.List[Chest] = chests @@ -515,13 +516,13 @@ class World: unknown_world_header_data = f.read_until(pointers.world_tiles) with Timer("World Tiles", display=True): - tiles = [] - while len(tiles) < world_size.x: + tm = TileMatrix() + while tm.size.x < world_size.x: column = [] while len(column) < world_size.y: readtiles = cls._read_tile_block(f, tileframeimportant) column = [*column, *readtiles] - tiles.append(column) + tm.add_column(column) unknown_world_tiles_data = f.read_until(pointers.chests) @@ -642,7 +643,7 @@ class World: time=time, events=events, dungeon_point=dungeon_point, world_evil=world_evil, saved_npcs=saved_npcs, altars_smashed=altars_smashed, is_hardmode=is_hardmode, shadow_orbs=shadow_orbs, bosses_defeated=bosses_defeated, anglers_quest=anglers_quest, - clouds=clouds, cultist_delay=cultist_delay, tiles=tiles, chests=chests, signs=signs, + clouds=clouds, cultist_delay=cultist_delay, tiles=tm, chests=chests, signs=signs, npcs=npcs, mobs=mobs, tile_entities=tile_entities, weighed_pressure_plates=weighed_pressure_plates, rooms=rooms, unknown_file_format_data=unknown_file_format_data, @@ -657,10 +658,10 @@ class World: with Timer("Footer", display=True): if not f.bool(): - raise Exception("Invalid footer") + raise InvalidFooterError("Invalid footer") if not f.string() == world.name: - raise Exception("Invalid footer") + raise InvalidFooterError("Invalid footer") if not f.int4() == world.id: - raise Exception("Invalid footer") + raise InvalidFooterError("Invalid footer") return world