1
Fork 0
mirror of https://github.com/Steffo99/lihzahrd.git synced 2024-11-21 23:54:23 +00:00

New things! A lot of new things!

This commit is contained in:
Steffo 2019-08-07 20:30:50 +02:00
parent 525b203b5c
commit cad4abed32

View file

@ -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)