mirror of
https://github.com/Steffo99/lihzahrd.git
synced 2024-11-21 15:44:24 +00:00
New things! A lot of new things!
This commit is contained in:
parent
525b203b5c
commit
cad4abed32
1 changed files with 447 additions and 63 deletions
|
@ -1,5 +1,9 @@
|
|||
import math
|
||||
import struct
|
||||
import uuid
|
||||
import enum
|
||||
import datetime
|
||||
import typing
|
||||
|
||||
|
||||
class Rect:
|
||||
|
@ -13,84 +17,464 @@ class Rect:
|
|||
return f"Rect(left={self.left}, right={self.right}, top={self.top}, bottom={self.bottom})"
|
||||
|
||||
|
||||
class World:
|
||||
@classmethod
|
||||
def create_from_file(cls, f):
|
||||
version = cls.int4(f)
|
||||
relogic = cls.string(f, 7)
|
||||
filetype = cls.byte(f)
|
||||
revision = cls.uint4(f)
|
||||
favorite = cls.uint8(f) != 0
|
||||
pointers = [cls.int4(f) for _ in range(cls.int2(f))]
|
||||
tileframeimportant_size = math.ceil(cls.int2(f) / 8)
|
||||
tileframeimportant = [cls.bool(f) for _ in range(tileframeimportant_size)]
|
||||
worldname = cls.string(f)
|
||||
worldid = cls.int4(f)
|
||||
# Not working from here on
|
||||
bounds = cls.rect(f)
|
||||
worldsize = (cls.int4(f), cls.int4(f))
|
||||
...
|
||||
class FileReader:
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
|
||||
@staticmethod
|
||||
def bit(f):
|
||||
data = f.read(1)
|
||||
return (data & 0b1000_0000,
|
||||
data & 0b0100_0000,
|
||||
data & 0b0010_0000,
|
||||
data & 0b0001_0000,
|
||||
data & 0b0000_1000,
|
||||
data & 0b0000_0100,
|
||||
data & 0b0000_0010,
|
||||
data & 0b0000_0001)
|
||||
def bool(self):
|
||||
return struct.unpack("?", self.file.read(1))[0]
|
||||
|
||||
@staticmethod
|
||||
def bool(f):
|
||||
return struct.unpack("?", f.read(1))[0]
|
||||
def int1(self):
|
||||
return struct.unpack("B", self.file.read(1))[0]
|
||||
|
||||
@staticmethod
|
||||
def byte(f):
|
||||
return struct.unpack("B", f.read(1))[0]
|
||||
def uint1(self):
|
||||
return struct.unpack("B", self.file.read(1))[0]
|
||||
|
||||
@staticmethod
|
||||
def int2(f):
|
||||
return struct.unpack("h", f.read(2))[0]
|
||||
def int2(self):
|
||||
return struct.unpack("h", self.file.read(2))[0]
|
||||
|
||||
@staticmethod
|
||||
def int4(f):
|
||||
return struct.unpack("i", f.read(4))[0]
|
||||
def uint2(self):
|
||||
return struct.unpack("H", self.file.read(2))[0]
|
||||
|
||||
@staticmethod
|
||||
def uint4(f):
|
||||
return struct.unpack("I", f.read(4))[0]
|
||||
def int4(self):
|
||||
return struct.unpack("i", self.file.read(4))[0]
|
||||
|
||||
@staticmethod
|
||||
def int8(f):
|
||||
return struct.unpack("q", f.read(8))[0]
|
||||
def uint4(self):
|
||||
return struct.unpack("I", self.file.read(4))[0]
|
||||
|
||||
@staticmethod
|
||||
def uint8(f):
|
||||
return struct.unpack("Q", f.read(8))[0]
|
||||
def int8(self):
|
||||
return struct.unpack("q", self.file.read(8))[0]
|
||||
|
||||
@staticmethod
|
||||
def single(f):
|
||||
return struct.unpack("f", f.read(4))[0]
|
||||
def uint8(self):
|
||||
return struct.unpack("Q", self.file.read(8))[0]
|
||||
|
||||
@staticmethod
|
||||
def double(f):
|
||||
return struct.unpack("d", f.read(8))[0]
|
||||
def single(self):
|
||||
return struct.unpack("f", self.file.read(4))[0]
|
||||
|
||||
@staticmethod
|
||||
def rect(f):
|
||||
left, right, top, bottom = struct.unpack("iiii", f.read(16))
|
||||
def double(self):
|
||||
return struct.unpack("d", self.file.read(8))[0]
|
||||
|
||||
def bit(self):
|
||||
data = struct.unpack("B", self.file.read(1))[0]
|
||||
return (bool(data & 0b1000_0000),
|
||||
bool(data & 0b0100_0000),
|
||||
bool(data & 0b0010_0000),
|
||||
bool(data & 0b0001_0000),
|
||||
bool(data & 0b0000_1000),
|
||||
bool(data & 0b0000_0100),
|
||||
bool(data & 0b0000_0010),
|
||||
bool(data & 0b0000_0001))
|
||||
|
||||
def rect(self):
|
||||
left, right, top, bottom = struct.unpack("iiii", self.file.read(16))
|
||||
return Rect(left, right, top, bottom)
|
||||
|
||||
@staticmethod
|
||||
def string(f, size=None):
|
||||
def string(self, size=None):
|
||||
if size is None:
|
||||
size = World.byte(f)
|
||||
return str(f.read(size), encoding="latin1")
|
||||
size = self.uint1()
|
||||
return str(self.file.read(size), encoding="latin1")
|
||||
|
||||
def uuid(self):
|
||||
# TODO: convert to uuid
|
||||
# https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray?view=netframework-4.8
|
||||
uuid_bytes = self.file.read(16)
|
||||
return uuid_bytes
|
||||
|
||||
def datetime(self):
|
||||
# TODO: convert to datetime
|
||||
# https://docs.microsoft.com/it-it/dotnet/api/system.datetime.kind?view=netframework-4.8#System_DateTime_Kind
|
||||
datetime_bytes = self.file.read(8)
|
||||
return datetime_bytes
|
||||
|
||||
|
||||
class Version:
|
||||
"""A Terraria version."""
|
||||
|
||||
_version_ids = {
|
||||
71: "1.2.0.3.1",
|
||||
77: "1.2.2",
|
||||
104: "1.2.3",
|
||||
140: "1.3.0.1",
|
||||
151: "1.3.0.4",
|
||||
153: "1.3.0.5",
|
||||
154: "1.3.0.6",
|
||||
155: "1.3.0.7",
|
||||
156: "1.3.0.8",
|
||||
170: "1.3.2",
|
||||
174: "1.3.3",
|
||||
178: "1.3.4",
|
||||
194: "1.3.5.3"
|
||||
}
|
||||
|
||||
def __init__(self, data: typing.Union[int, str]):
|
||||
if isinstance(data, int):
|
||||
self.id = data
|
||||
else:
|
||||
for version in self._version_ids:
|
||||
if self._version_ids[version] == data:
|
||||
self.id = version
|
||||
break
|
||||
else:
|
||||
raise ValueError("No such version")
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
# TODO: Add all versions
|
||||
try:
|
||||
return self._version_ids[self.id]
|
||||
except KeyError:
|
||||
return "Unknown"
|
||||
|
||||
def __repr__(self):
|
||||
return f"Version({self.id})"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.id > other
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.id < other
|
||||
|
||||
|
||||
class GeneratorInfo:
|
||||
"""Information about the world generator."""
|
||||
|
||||
def __init__(self, seed, version):
|
||||
self.seed = seed
|
||||
"""The seed this world was generated with."""
|
||||
|
||||
self.version = version
|
||||
"""The version of the generator that created this world."""
|
||||
|
||||
|
||||
class Coordinates:
|
||||
"""A pair of coordinates."""
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __repr__(self):
|
||||
return f"Coordinates({self.x}, {self.y})"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.x}, {self.y}"
|
||||
|
||||
|
||||
class MoonStyle(enum.IntEnum):
|
||||
"""All possible moon styles."""
|
||||
WHITE = 0
|
||||
ORANGE = 1
|
||||
RINGED_GREEN = 2
|
||||
|
||||
|
||||
class FourPartSplit:
|
||||
"""A world property split in four parts, separated by three vertical lines at a certain x coordinate."""
|
||||
def __init__(self, separators: typing.List[int], properties: typing.List):
|
||||
self.separators: typing.List[int] = separators
|
||||
"""The three x coordinates of the vertical separators, in increasing order."""
|
||||
|
||||
self.properties: typing.List = properties
|
||||
"""The four properties, in order:
|
||||
|
||||
- The far left property, the one between the left world edge and the first separator.
|
||||
- The nearby left property, between the first and the second separator.
|
||||
- The nearby right property, between the second and the third separator.
|
||||
- The far right property, between the third separator and the right world edge."""
|
||||
|
||||
def __repr__(self):
|
||||
return f"FourPartSplit({repr(self.separators)}, {repr(self.properties)})"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.far_left} [{self.separators[0]}] {self.nearby_left} [{self.separators[1]}] {self.nearby_right} [{self.separators[2]}] {self.far_right}"
|
||||
|
||||
def get_property_at_x(self, x: int):
|
||||
if x < self.separators[0]:
|
||||
return self.properties[0]
|
||||
elif x < self.separators[1]:
|
||||
return self.properties[1]
|
||||
elif x < self.separators[2]:
|
||||
return self.properties[2]
|
||||
else:
|
||||
return self.properties[3]
|
||||
|
||||
@property
|
||||
def far_left(self):
|
||||
return self.properties[0]
|
||||
|
||||
@far_left.setter
|
||||
def far_left(self, value):
|
||||
self.properties[0] = value
|
||||
|
||||
@property
|
||||
def nearby_left(self):
|
||||
return self.properties[1]
|
||||
|
||||
@nearby_left.setter
|
||||
def nearby_left(self, value):
|
||||
self.properties[1] = value
|
||||
|
||||
@property
|
||||
def nearby_right(self):
|
||||
return self.properties[2]
|
||||
|
||||
@nearby_right.setter
|
||||
def nearby_right(self, value):
|
||||
self.properties[2] = value
|
||||
|
||||
@property
|
||||
def far_right(self):
|
||||
return self.properties[2]
|
||||
|
||||
@far_right.setter
|
||||
def far_right(self, value):
|
||||
self.properties[2] = value
|
||||
|
||||
|
||||
class WorldStyles:
|
||||
"""The styles of various world elements."""
|
||||
def __init__(self,
|
||||
moon: MoonStyle,
|
||||
trees: FourPartSplit,
|
||||
moss: FourPartSplit,):
|
||||
self.moon: MoonStyle = moon
|
||||
self.trees: FourPartSplit = trees
|
||||
self.moss: FourPartSplit = moss
|
||||
|
||||
|
||||
class WorldBackgrounds:
|
||||
"""The backgrounds of various world biomes."""
|
||||
def __init__(self,
|
||||
bg_underground_snow,
|
||||
bg_underground_jungle,
|
||||
bg_hell,
|
||||
bg_forest,
|
||||
bg_corruption,
|
||||
bg_jungle,
|
||||
bg_snow,
|
||||
bg_hallow,
|
||||
bg_crimson,
|
||||
bg_desert,
|
||||
bg_ocean,
|
||||
bg_cloud):
|
||||
self.bg_underground_snow = bg_underground_snow
|
||||
self.bg_underground_jungle = bg_underground_jungle
|
||||
self.bg_hell = bg_hell
|
||||
self.bg_forest = bg_forest
|
||||
self.bg_corruption = bg_corruption
|
||||
self.bg_jungle = bg_jungle
|
||||
self.bg_snow = bg_snow
|
||||
self.bg_hallow = bg_hallow
|
||||
self.bg_crimson = bg_crimson
|
||||
self.bg_desert = bg_desert
|
||||
self.bg_ocean = bg_ocean
|
||||
self.bg_cloud = bg_cloud
|
||||
|
||||
|
||||
|
||||
class World:
|
||||
"""The Python representation of a Terraria world."""
|
||||
def __init__(self,
|
||||
version: Version,
|
||||
savefile_type: int,
|
||||
revision: int,
|
||||
is_favorite: bool,
|
||||
name: str,
|
||||
generator: GeneratorInfo,
|
||||
uuid_: uuid.UUID,
|
||||
id_: int,
|
||||
bounds: Rect,
|
||||
size: Coordinates,
|
||||
is_expert: bool,
|
||||
created_on,
|
||||
):
|
||||
|
||||
self.version: Version = version
|
||||
"""The game version when this savefile was last saved."""
|
||||
|
||||
self.savefile_type = savefile_type
|
||||
"""The format of the save file. Should be 2 for all versions following 1.2."""
|
||||
|
||||
self.revision: int = revision
|
||||
"""The number of times this world was saved."""
|
||||
|
||||
self.is_favorite: bool = is_favorite
|
||||
"""If the world is marked as favorite or not."""
|
||||
|
||||
self.name: str = name
|
||||
"""The name the world was given at creation. Doesn't always match the filename."""
|
||||
|
||||
self.generator: GeneratorInfo = generator
|
||||
"""Information about the generation of this world."""
|
||||
|
||||
self.uuid: uuid.UUID = uuid_
|
||||
"""The Universally Unique ID of this world."""
|
||||
|
||||
self.id: int = id_
|
||||
"""The world id. Used to name the minimap file."""
|
||||
|
||||
self.bounds: Rect = bounds
|
||||
"""The world size in pixels."""
|
||||
|
||||
self.size: Coordinates = size
|
||||
"""The world size in tiles."""
|
||||
|
||||
self.is_expert: bool = is_expert
|
||||
"""If the world is in expert mode or not."""
|
||||
|
||||
self.created_on = created_on
|
||||
"""The date and time this world was created in."""
|
||||
|
||||
@classmethod
|
||||
def create_from_file(cls, file):
|
||||
f = FileReader(file)
|
||||
|
||||
version = Version(f.int4())
|
||||
relogic = f.string(7)
|
||||
savefile_type = f.uint1()
|
||||
if version != Version("1.3.5.3") or relogic != "relogic" or savefile_type != 2:
|
||||
raise NotImplementedError("This parser can only read Terraria 1.3.5.3 save files.")
|
||||
|
||||
revision = f.uint4()
|
||||
is_favorite = f.uint8() != 0
|
||||
|
||||
pointers = [f.int4() for _ in range(f.int2())]
|
||||
tileframeimportant_size = math.ceil(f.int2() / 8)
|
||||
tileframeimportant = []
|
||||
for _ in range(tileframeimportant_size):
|
||||
current_bit = f.bit()
|
||||
tileframeimportant = [*tileframeimportant, *current_bit]
|
||||
|
||||
name = f.string()
|
||||
seed = f.string()
|
||||
generator_version = f.int4()
|
||||
uuid_ = f.uuid()
|
||||
id_ = f.int8()
|
||||
bounds = f.rect()
|
||||
world_size = Coordinates(y=f.int4(), x=f.int4())
|
||||
is_expert = f.bool()
|
||||
created_on = f.datetime()
|
||||
|
||||
world_styles = WorldStyles(moon=MoonStyle(f.uint1()),
|
||||
trees=FourPartSplit(separators=[f.int4(), f.int4(), f.int4()],
|
||||
properties=[f.int4(), f.int4(), f.int4(), f.int4()]),
|
||||
moss=FourPartSplit(separators=[f.int4(), f.int4(), f.int4()],
|
||||
properties=[f.int4(), f.int4(), f.int4(), f.int4()]))
|
||||
|
||||
bg_underground_snow = f.int4()
|
||||
bg_underground_jungle = f.int4()
|
||||
bg_hell = f.int4()
|
||||
spawn_point = (f.int4(), f.int4())
|
||||
underground_level = f.double()
|
||||
cavern_level = f.double()
|
||||
current_time = f.double()
|
||||
is_daytime = f.bool()
|
||||
moon_phase = f.uint4()
|
||||
blood_moon = f.bool()
|
||||
eclipse = f.bool()
|
||||
dungeon_point = (f.int4(), f.int4())
|
||||
is_crimson = f.bool()
|
||||
defeated_eye_of_cthulhu = f.bool() # Possibly. I'm not sure.
|
||||
defeated_eater_of_worlds = f.bool() # Possibly. I'm not sure.
|
||||
defeated_skeletron = f.bool() # Possibly. I'm not sure.
|
||||
defeated_queen_bee = f.bool()
|
||||
defeated_the_twins = f.bool()
|
||||
defeated_the_destroyer = f.bool()
|
||||
defeated_skeletron_prime = f.bool()
|
||||
defeated_any_mechnical_boss = f.bool()
|
||||
defeated_plantera = f.bool()
|
||||
defeated_golem = f.bool()
|
||||
defeated_king_slime = f.bool()
|
||||
saved_goblin_tinkerer = f.bool()
|
||||
saved_wizard = f.bool()
|
||||
saved_mechanic = f.bool()
|
||||
defeated_goblin_army = f.bool()
|
||||
defeated_clown = f.bool()
|
||||
defeated_frost_moon = f.bool()
|
||||
defeated_pirates = f.bool()
|
||||
smashed_shadow_orb = f.bool()
|
||||
spawn_meteor = f.bool()
|
||||
smashed_shadow_orb_mod3 = f.int4()
|
||||
smashed_altars_count = f.int4()
|
||||
is_hardmode = f.bool()
|
||||
invasion_delay = f.int4()
|
||||
invasion_size = f.int4()
|
||||
invasion_type = f.int4()
|
||||
invasion_position = f.double()
|
||||
time_left_slime_rain = f.double()
|
||||
cooldown_sundial = f.uint1()
|
||||
is_raining = f.bool()
|
||||
time_left_rain = f.int4()
|
||||
max_rain = f.single() # ???
|
||||
hardmode_ore_1 = f.int4()
|
||||
hardmode_ore_2 = f.int4()
|
||||
hardmode_ore_3 = f.int4()
|
||||
bg_forest = f.int1()
|
||||
bg_corruption = f.int1()
|
||||
bg_jungle = f.int1()
|
||||
bg_snow = f.int1()
|
||||
bg_hallow = f.int1()
|
||||
bg_crimson = f.int1()
|
||||
bg_desert = f.int1()
|
||||
bg_ocean = f.int1()
|
||||
bg_cloud = f.int4() # ???
|
||||
cloud_number = f.int2() # ???
|
||||
wind_speed = f.single() # ???
|
||||
angler_today_quest_completed_by_count = f.uint1()
|
||||
angler_today_quest_completed_by = []
|
||||
for _ in range(angler_today_quest_completed_by_count):
|
||||
angler_today_quest_completed_by.append(f.string())
|
||||
saved_angler = f.bool()
|
||||
angler_today_quest_target = f.int4()
|
||||
saved_stylist = f.bool()
|
||||
saved_tax_collector = f.bool()
|
||||
invasion_size_start = f.int4() # ???
|
||||
cultist_delay = f.int4() # ???
|
||||
...
|
||||
mob_types_count = f.int2()
|
||||
mob_kills = {}
|
||||
for mob_id in range(mob_types_count):
|
||||
mob_kills[mob_id] = f.int4()
|
||||
fast_forward_time = f.bool()
|
||||
defeated_duke_fishron = f.bool()
|
||||
defeated_moon_lord = f.bool()
|
||||
defeated_pumpking = f.bool()
|
||||
defeated_mourning_wood = f.bool()
|
||||
defeated_ice_queen = f.bool()
|
||||
defeated_santa_nk1 = f.bool()
|
||||
defeated_everscream = f.bool()
|
||||
defeated_pillar_solar = f.bool()
|
||||
defeated_pillar_vortex = f.bool()
|
||||
defeated_pillar_nebula = f.bool()
|
||||
defeated_pillar_stardust = f.bool()
|
||||
solar_pillar_active = f.bool()
|
||||
vortex_pillar_active = f.bool()
|
||||
nebula_pillar_active = f.bool()
|
||||
stardust_pillar_active = f.bool()
|
||||
lunar_events_active = f.bool()
|
||||
party_center_active = f.bool()
|
||||
party_natural_active = f.bool()
|
||||
party_cooldown = f.int4()
|
||||
partying_npcs_count = f.int4()
|
||||
partying_npcs = []
|
||||
for _ in range(partying_npcs_count):
|
||||
partying_npcs.append(f.int4())
|
||||
is_sandstorm = f.bool()
|
||||
time_left_sandstorm = f.int4()
|
||||
sandstorm_severity = f.single() # ???
|
||||
sandstorm_intended_severity = f.single() # ???
|
||||
saved_bartender = f.bool()
|
||||
defeated_old_ones_army_tier_1 = f.bool()
|
||||
defeated_old_ones_army_tier_2 = f.bool()
|
||||
defeated_old_ones_army_tier_3 = f.bool()
|
||||
# Tile data starts here
|
||||
...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("sampleworld.wld", "rb") as f:
|
||||
with open("Small_Example.wld", "rb") as f:
|
||||
w = World.create_from_file(f)
|
||||
|
|
Loading…
Reference in a new issue