diff --git a/__main__.py b/__main__.py index aa51226..1ee14ab 100644 --- a/__main__.py +++ b/__main__.py @@ -1,5 +1,5 @@ import lihzahrd -world = lihzahrd.World.create_from_file("test-1404.wld") +world = lihzahrd.World.create_from_file("test-1404j.wld") breakpoint() diff --git a/lihzahrd/__init__.py b/lihzahrd/__init__.py index 2b33900..f57f539 100644 --- a/lihzahrd/__init__.py +++ b/lihzahrd/__init__.py @@ -1,5 +1,5 @@ from .world import World -from . import chests, fileutils, header, npcs, pressureplates, signs, tileentities, tiles, townmanager +from . import bestiary, chests, fileutils, header, journeypowers, npcs, pressureplates, signs, tileentities, tiles, townmanager -__all__ = ["World", "chests", "fileutils", "header", "npcs", "pressureplates", "signs", "tileentities", "tiles", +__all__ = ["World", "bestiary", "chests", "fileutils", "header", "journeypowers", "npcs", "pressureplates", "signs", "tileentities", "tiles", "townmanager"] diff --git a/lihzahrd/bestiary/__init__.py b/lihzahrd/bestiary/__init__.py new file mode 100755 index 0000000..95532c9 --- /dev/null +++ b/lihzahrd/bestiary/__init__.py @@ -0,0 +1,3 @@ +from .bestiary import Bestiary + +__all__ = ["Bestiary"] diff --git a/lihzahrd/bestiary/bestiary.py b/lihzahrd/bestiary/bestiary.py new file mode 100755 index 0000000..266825d --- /dev/null +++ b/lihzahrd/bestiary/bestiary.py @@ -0,0 +1,23 @@ +import typing + +class Bestiary: + """A bestiary entry.""" + + __slots__ = "npc_chats_count", "npc_chats_data", "npc_kill_count","npc_kill_data", "npc_sighting_count", "npc_sighting_data" + + def __init__(self, + npc_chats_count: int, + npc_chats_data: typing.Dict[str, int], + npc_kill_count: int, + npc_kill_data: typing.List[str], + npc_sighting_count: int, + npc_sighting_data: typing.List[str],): + self.npc_chats_count: int = npc_chats_count + self.npc_chats_data: typing.Dict[str, int] = npc_chats_data + self.npc_kill_count: int = npc_kill_count + self.npc_kill_data: typing.List[str] = npc_kill_data + self.npc_sighting_count: int = npc_sighting_count + self.npc_sighting_data: typing.List[str] = npc_sighting_data + + def __repr__(self): + return f"" diff --git a/lihzahrd/fileutils/pointers.py b/lihzahrd/fileutils/pointers.py index 0bd6a8c..71aa761 100644 --- a/lihzahrd/fileutils/pointers.py +++ b/lihzahrd/fileutils/pointers.py @@ -11,6 +11,8 @@ class Pointers: tile_entities: int, pressure_plates: int, town_manager: int, + bestiary: int, + journey_powers: int, footer: int, *unknown): self.file_format: int = 0 @@ -22,5 +24,7 @@ class Pointers: self.tile_entities: int = tile_entities self.pressure_plates: int = pressure_plates self.town_manager: int = town_manager + self.bestiary: int = bestiary + self.journey_powers: int = journey_powers self.footer: int = footer self.unknown: typing.List[int] = list(unknown) diff --git a/lihzahrd/journeypowers/__init__.py b/lihzahrd/journeypowers/__init__.py new file mode 100755 index 0000000..9bae699 --- /dev/null +++ b/lihzahrd/journeypowers/__init__.py @@ -0,0 +1,3 @@ +from .journeypowers import JourneyPowers + +__all__ = ["JourneyPowers"] diff --git a/lihzahrd/journeypowers/journeypowers.py b/lihzahrd/journeypowers/journeypowers.py new file mode 100755 index 0000000..07a07d9 --- /dev/null +++ b/lihzahrd/journeypowers/journeypowers.py @@ -0,0 +1,45 @@ +import typing + +class JourneyPowers: + """Journey mode powers settings. Spawn rate does not appear to be stored in the world.""" + + __slots__ = "freeze_time", "god_mode", "time_rate", "freeze_rain", "freeze_wind", "far_placement_range", "difficulty", "freeze_biome_spread", "spawn_rate" + + def __init__(self, + freeze_time: typing.Optional[bool] = None, + god_mode: typing.Optional[bool] = None, + time_rate: typing.Optional[float] = None, + freeze_rain: typing.Optional[bool] = None, + freeze_wind: typing.Optional[bool] = None, + far_placement_range: typing.Optional[bool] = None, + difficulty: typing.Optional[float] = None, + freeze_biome_spread: typing.Optional[bool] = None,): + + self.freeze_time: bool = freeze_time + """Can time be frozen.""" + + self.god_mode: bool = god_mode + """Can god mode be enabled.""" + + self.time_rate: float = time_rate + """How fast does time go, 1x to 24x. Value ranges from 0.0 to 1.0.""" + + self.freeze_rain: bool = freeze_rain + """Can the rain change.""" + + self.freeze_wind: bool = freeze_wind + """Can the wind speed and direction change.""" + + self.far_placement_range: bool = far_placement_range + """Can players place blocks further than normal.""" + + self.difficulty: float = difficulty + """Enemy difficulty scaling, 0.5x to 3x. Value ranges from 0.0 to 1.0.""" + + self.freeze_biome_spread: bool = freeze_biome_spread + """Can evil biomes & the hallow spread.""" + + def __repr__(self): + return f"" diff --git a/lihzahrd/npcs/npc.py b/lihzahrd/npcs/npc.py index 9493438..1e88a8f 100644 --- a/lihzahrd/npcs/npc.py +++ b/lihzahrd/npcs/npc.py @@ -6,13 +6,14 @@ from ..fileutils import Coordinates class NPC: """A NPC somewhere in the world.""" - __slots__ = "type", "name", "position", "home" + __slots__ = "type", "name", "position", "home", "variation_index" def __init__(self, - type_: EntityType, - name: str, - position: Coordinates, - home: typing.Optional[Coordinates] = None): + type_: EntityType, + name: str, + position: Coordinates, + variation_index: int, + home: typing.Optional[Coordinates] = None,): self.type: EntityType = type_ """The NPC this object represents.""" @@ -25,5 +26,8 @@ class NPC: self.home: typing.Optional[Coordinates] = home """The coordinates of the home of this NPC, or None if the NPC is homeless.""" + self.variation_index: int = variation_index + """Unsure, but seems to be an index to different possible NPC variations.""" + def __repr__(self): return f"" diff --git a/lihzahrd/world.py b/lihzahrd/world.py index ed1e53f..12306ae 100644 --- a/lihzahrd/world.py +++ b/lihzahrd/world.py @@ -4,6 +4,8 @@ import typing from .fileutils import * from .header import * from .tiles import * +from .bestiary import * +from .journeypowers import * from .chests import * from .signs import * from .npcs import * @@ -49,6 +51,8 @@ class World: clouds: Clouds, cultist_delay: int, tiles: TileMatrix, + bestiary: Bestiary, + journey_powers: JourneyPowers, chests: typing.List[Chest], signs: typing.List[Sign], npcs: typing.List[NPC], @@ -69,7 +73,9 @@ class World: unknown_npcs_data: bytes = b"", unknown_tile_entities_data: bytes = b"", unknown_pressure_plates_data: bytes = b"", - unknown_town_manager_data: bytes = b""): + unknown_town_manager_data: bytes = b"", + unknown_bestiary_data: bytes = b"", + unknown_journey_powers_data: bytes = b""): self.version: Version = version """The game version when this savefile was last saved.""" @@ -210,6 +216,12 @@ class World: self.unknown_pressure_plates_data: bytes = unknown_pressure_plates_data self.unknown_town_manager_data: bytes = unknown_town_manager_data + self.bestiary: Bestiary = bestiary + """Information about the bestiary, including sightings, kills and takling to NPCs.""" + + self.journey_powers: JourneyPowers= journey_powers + """Status of powers available in Journey mode.""" + def __repr__(self): return f'' @@ -676,10 +688,14 @@ class World: if is_homeless: npc_home = None + npc_flags = f.bits() + npc_variation_index = 0 if npc_flags[0] else f.int4() + npc = NPC(type_=npc_type, name=npc_name, position=npc_position, - home=npc_home) + home=npc_home, + variation_index=npc_variation_index) npcs.append(npc) while f.bool(): @@ -735,7 +751,56 @@ class World: room = Room(npc=EntityType(f.int4()), position=Coordinates(f.int4(), f.int4())) rooms.append(room) - unknown_town_manager_data = f.read_until(pointers.footer) + unknown_town_manager_data = f.read_until(pointers.bestiary) + + bestiary_kill_count = f.int4() + bestiary_kill_data = {f.string(): f.int4() for _ in range(bestiary_kill_count)} + bestiary_sighting_count = f.int4() + bestiary_sighting_data = [f.string() for _ in range(bestiary_sighting_count)] + bestiary_chat_count = f.int4() + bestiary_chat_data = [f.string() for _ in range(bestiary_chat_count)] + bestiary = Bestiary(bestiary_chat_count, + bestiary_chat_data, + bestiary_kill_count, + bestiary_kill_data, + bestiary_sighting_count, + bestiary_sighting_data) + unknown_bestiary_data = f.read_until(pointers.journey_powers) + + while f.bool(): + journey_powers = JourneyPowers() + power_id = f.int2() + if power_id == 0: + freeze_time = f.bool() + journey_powers.freeze_time = freeze_time + elif power_id == 5: + god_mode = f.bool() + journey_powers.god_mode = god_mode + elif power_id == 8: + time_rate = f.single() + journey_powers.time_rate = time_rate + elif power_id == 9: + freeze_rain = f.bool() + journey_powers.freeze_rain = freeze_rain + elif power_id == 10: + freeze_wind = f.bool() + journey_powers.freeze_wind = freeze_wind + elif power_id == 11: + far_placement_range = f.bool() + journey_powers.far_placement_range = far_placement_range + elif power_id == 12: + difficulty = f.single() + journey_powers.difficulty = difficulty + elif power_id == 13: + freeze_biome_spread = f.bool() + journey_powers.freeze_biome_spread = freeze_biome_spread + elif power_id == -1: + spawn_rate: f.single() + journey_powers.spawn_rate = spawn_rate + else: + print(f"Unknown id: {power_id}") + + unknown_journey_powers_data = f.read_until(pointers.footer) # Object creation world = cls(version=version, savefile_type=savefile_type, revision=revision, is_favorite=is_favorite, @@ -751,6 +816,7 @@ class World: weighed_pressure_plates=weighed_pressure_plates, rooms=rooms, halloween_today=halloween_today, xmas_today=xmas_today, treetop_variants=treetop_variants, saved_ore_tiers=saved_ore_tiers, pets=pets, + bestiary=bestiary, journey_powers=journey_powers, unknown_file_format_data=unknown_file_format_data, unknown_world_header_data=unknown_world_header_data, unknown_world_tiles_data=unknown_world_tiles_data, @@ -759,7 +825,9 @@ class World: unknown_npcs_data=unknown_npcs_data, unknown_tile_entities_data=unknown_tile_entities_data, unknown_pressure_plates_data=unknown_pressure_plates_data, - unknown_town_manager_data=unknown_town_manager_data) + unknown_town_manager_data=unknown_town_manager_data, + unknown_bestiary_data=unknown_bestiary_data, + unknown_journey_powers_data=unknown_journey_powers_data) # Footer if not f.bool():