From 06562b0572986a1e1d83e5f95a76160ca8c9da2f Mon Sep 17 00:00:00 2001 From: Dale Floer Date: Fri, 29 May 2020 23:37:53 -0700 Subject: [PATCH] First pass at 1.4.0.4 world header parsing. --- __main__.py | 2 +- lihzahrd/header/__init__.py | 8 ++ lihzahrd/header/backgrounds.py | 15 +++- lihzahrd/header/bossesdefeated.py | 6 +- lihzahrd/header/events.py | 8 +- lihzahrd/header/lanternevent.py | 29 +++++++ lihzahrd/header/moonstyle.py | 7 ++ lihzahrd/header/pets.py | 19 +++++ lihzahrd/header/savednpcs.py | 9 ++- lihzahrd/header/savedoretiers.py | 17 ++++ lihzahrd/header/treetopvariants.py | 10 +++ lihzahrd/world.py | 122 +++++++++++++++++++++-------- 12 files changed, 213 insertions(+), 39 deletions(-) create mode 100755 lihzahrd/header/lanternevent.py create mode 100755 lihzahrd/header/pets.py create mode 100755 lihzahrd/header/savedoretiers.py create mode 100755 lihzahrd/header/treetopvariants.py diff --git a/__main__.py b/__main__.py index f55507a..aa51226 100644 --- a/__main__.py +++ b/__main__.py @@ -1,5 +1,5 @@ import lihzahrd -world = lihzahrd.World.create_from_file("Casarao.wld") +world = lihzahrd.World.create_from_file("test-1404.wld") breakpoint() diff --git a/lihzahrd/header/__init__.py b/lihzahrd/header/__init__.py index bd2b544..33988d3 100644 --- a/lihzahrd/header/__init__.py +++ b/lihzahrd/header/__init__.py @@ -10,18 +10,22 @@ from .generatorinfo import GeneratorInfo from .hardmodeore import HardmodeTier1Ore, HardmodeTier2Ore, HardmodeTier3Ore from .invasion import Invasion from .invasiontype import InvasionType +from .lanternevent import LanternEvent from .lunarevents import LunarEvents from .moonphase import MoonPhase from .moonstyle import MoonStyle from .oldonesarmytiers import OldOnesArmyTiers from .party import Party +from .pets import Pets from .pillarsinfo import PillarsInfo from .rain import Rain from .sandstorm import Sandstorm from .savednpcs import SavedNPCs +from .savedoretiers import SavedOreTiers from .shadoworbs import ShadowOrbs from .styles import Styles from .time import Time +from .treetopvariants import TreetopVariants from .version import Version from .worldeviltype import WorldEvilType @@ -40,18 +44,22 @@ __all__ = [ "HardmodeTier3Ore", "Invasion", "InvasionType", + "LanternEvent", "LunarEvents", "MoonPhase", "MoonStyle", "OldOnesArmyTiers", "Party", + "Pets", "PillarsInfo", "Rain", "Sandstorm", "SavedNPCs", + "SavedOreTiers", "ShadowOrbs", "Styles", "Time", + "TreetopVariants", "Version", "WorldEvilType" ] diff --git a/lihzahrd/header/backgrounds.py b/lihzahrd/header/backgrounds.py index c116339..2872038 100644 --- a/lihzahrd/header/backgrounds.py +++ b/lihzahrd/header/backgrounds.py @@ -11,7 +11,12 @@ class Backgrounds: bg_hallow: int, bg_crimson: int, bg_desert: int, - bg_ocean: int): + bg_ocean: int, + new_bg_1: int, + new_bg_2: int, + new_bg_3: int, + new_bg_4: int, + new_bg_5: int,): self.bg_underground_snow: int = bg_underground_snow self.bg_underground_jungle: int = bg_underground_jungle self.bg_hell: int = bg_hell @@ -23,8 +28,14 @@ class Backgrounds: self.bg_crimson: int = bg_crimson self.bg_desert: int = bg_desert self.bg_ocean: int = bg_ocean + self.new_bg_1: int = new_bg_1 + self.new_bg_2: int = new_bg_2 + self.new_bg_3: int = new_bg_3 + self.new_bg_4: int = new_bg_4 + self.new_bg_5: int = new_bg_5 def __repr__(self): return f"WorldBackgrounds({self.bg_underground_snow}, {self.bg_underground_jungle}, {self.bg_hell}," \ f" {self.bg_forest}, {self.bg_corruption}, {self.bg_jungle}, {self.bg_snow}, {self.bg_hallow}," \ - f" {self.bg_crimson}, {self.bg_desert}, {self.bg_ocean})" + f" {self.bg_crimson}, {self.bg_desert}, {self.bg_ocean}," \ + f" {self.new_bg_1}, {self.new_bg_2}, {self.new_bg_3}, {self.new_bg_4}, {self.new_bg_5})" diff --git a/lihzahrd/header/bossesdefeated.py b/lihzahrd/header/bossesdefeated.py index 258faec..28fa95c 100644 --- a/lihzahrd/header/bossesdefeated.py +++ b/lihzahrd/header/bossesdefeated.py @@ -29,7 +29,9 @@ class BossesDefeated: martian_madness: bool, lunatic_cultist: bool, lunar_pillars: PillarsInfo, - old_ones_army: OldOnesArmyTiers): + old_ones_army: OldOnesArmyTiers, + empress_of_light: bool, + queen_slime: bool): self.eye_of_cthulhu: bool = eye_of_cthulhu self.eater_of_worlds: bool = eater_of_worlds self.skeletron: bool = skeletron @@ -59,6 +61,8 @@ class BossesDefeated: self.lunatic_cultist: bool = lunatic_cultist self.lunar_pillars: PillarsInfo = lunar_pillars self.old_ones_army: OldOnesArmyTiers = old_ones_army + self.empress_of_light: bool = empress_of_light + self.queen_slime: bool = queen_slime def __repr__(self): return f"" diff --git a/lihzahrd/header/events.py b/lihzahrd/header/events.py index 3120207..f610734 100644 --- a/lihzahrd/header/events.py +++ b/lihzahrd/header/events.py @@ -3,7 +3,7 @@ from .rain import Rain from .party import Party from .sandstorm import Sandstorm from .lunarevents import LunarEvents - +from .lanternevent import LanternEvent class Events: """Information about the ongoing world events.""" @@ -15,7 +15,8 @@ class Events: rain: Rain, party: Party, sandstorm: Sandstorm, - lunar_events: LunarEvents): + lunar_events: LunarEvents, + lantern_night: LanternEvent): self.blood_moon: bool = blood_moon """If the current moon is a Blood Moon.""" @@ -40,5 +41,8 @@ class Events: self.lunar_events: LunarEvents = lunar_events """Information about the currently ongoing Lunar Events.""" + self.lantern_night: LanternEvent = lantern_night + """Information about the currently ongoing lantern night.""" + def __repr__(self): return f"" diff --git a/lihzahrd/header/lanternevent.py b/lihzahrd/header/lanternevent.py new file mode 100755 index 0000000..c50c9c2 --- /dev/null +++ b/lihzahrd/header/lanternevent.py @@ -0,0 +1,29 @@ +import typing + + +class LanternEvent: + """Lantern Night event related information.""" + def __init__(self, + nights_on_cooldown: int, + genuine: bool, + manual: bool, + next_night_is_lantern_night: bool): + self.nights_on_cooldown: int = nights_on_cooldown + """How many nights before the next lantern night can happen.""" + + self.genuine: bool = genuine + """Was this night started by the game spontaneously?""" + + self.manual: bool = manual + """Was this night started by the player?""" + + self.next_night_is_lantern_night:bool = next_night_is_lantern_night + """Is the next night a lantern night?""" + + def __repr__(self): + return f"WorldLanternNight(nights_on_cooldown={self.nights_on_cooldown}," \ + f" genuine={self.genuine}, manual={self.manual}, nights_on_cooldown={self.nights_on_cooldown})" + + @property + def is_active(self): + return self.genuine or self.manual # ToDo: Test this! diff --git a/lihzahrd/header/moonstyle.py b/lihzahrd/header/moonstyle.py index c6b4ecc..edaa8c3 100644 --- a/lihzahrd/header/moonstyle.py +++ b/lihzahrd/header/moonstyle.py @@ -6,6 +6,13 @@ class MoonStyle(enum.IntEnum): WHITE = 0 ORANGE = 1 RINGED_GREEN = 2 + BLUE = 3 + ICE = 4 + GREEN = 5 + PINK = 6 + PINK_ORANGE = 7 + TRIPLE_PURPLE = 8 + def __repr__(self): return f"{self.__class__.__name__}.{self.name}" diff --git a/lihzahrd/header/pets.py b/lihzahrd/header/pets.py new file mode 100755 index 0000000..fe84956 --- /dev/null +++ b/lihzahrd/header/pets.py @@ -0,0 +1,19 @@ +import typing + +class Pets: + """Pet related information""" + def __init__(self, + cat: bool, + dog: bool, + bunny: bool): + self.cat: bool = cat + """Has the cat been bought.""" + + self.dog: int = dog + """Has the cat been bought.""" + + self.bunny: float = bunny + """Has the bunny been bought.""" + + def __repr__(self): + return f"WorldPets(cat={self.cat}, dog={self.dog}, bunny={self.bunny})" diff --git a/lihzahrd/header/savednpcs.py b/lihzahrd/header/savednpcs.py index 730d746..f2bc2f5 100644 --- a/lihzahrd/header/savednpcs.py +++ b/lihzahrd/header/savednpcs.py @@ -6,7 +6,9 @@ class SavedNPCs: angler: bool, stylist: bool, tax_collector: bool, - bartender: bool): + bartender: bool, + golfer: bool, + advanced_combat: bool): self.goblin_tinkerer: bool = goblin_tinkerer self.wizard: bool = wizard self.mechanic: bool = mechanic @@ -14,8 +16,11 @@ class SavedNPCs: self.stylist: bool = stylist self.tax_collector: bool = tax_collector self.bartender: bool = bartender + self.golfer: bool = golfer + self.advanced_combat: bool = advanced_combat + """Was the Advanced Combat Technique Book used.""" def __repr__(self): return f"SavedNPCs(goblin_tinkerer={self.goblin_tinkerer}, wizard={self.wizard}, mechanic={self.mechanic}," \ f" angler={self.angler}, stylist={self.stylist}, tax_collector={self.tax_collector}," \ - f" bartender={self.bartender})" + f" bartender={self.bartender}, golfer={self.golfer}, advanced_combat={self.advanced_combat}" diff --git a/lihzahrd/header/savedoretiers.py b/lihzahrd/header/savedoretiers.py new file mode 100755 index 0000000..8230033 --- /dev/null +++ b/lihzahrd/header/savedoretiers.py @@ -0,0 +1,17 @@ +import typing + +class SavedOreTiers: + """Information about the treetop variants in the worls""" + def __init__(self, + saved_ore_tier_copper: int, + saved_ore_tier_iron: int, + saved_ore_tier_silver: int, + saved_ore_tier_gold: int, + ): + self.copper: int = saved_ore_tier_copper + self.iron: int = saved_ore_tier_iron + self.silver: int = saved_ore_tier_copper + self.gold: int = saved_ore_tier_gold + + def __repr__(self): + return f"WorldSavedOreTier(copper={self.copper}, iron={self.iron}, silver={self.silver}, gold={self.gold})" diff --git a/lihzahrd/header/treetopvariants.py b/lihzahrd/header/treetopvariants.py new file mode 100755 index 0000000..6026a05 --- /dev/null +++ b/lihzahrd/header/treetopvariants.py @@ -0,0 +1,10 @@ +import typing + +class TreetopVariants: + """Information about the treetop variants in the worls""" + def __init__(self, + treetop_variants: typing.List[str]): + self.treetop_variants: typing.List[str] = treetop_variants + + def __repr__(self): + return f"WorldTreetopVariants({self.treetop_variants})" diff --git a/lihzahrd/world.py b/lihzahrd/world.py index 350b422..c09b7c3 100644 --- a/lihzahrd/world.py +++ b/lihzahrd/world.py @@ -27,7 +27,9 @@ class World: id_: int, bounds: Rect, size: Coordinates, - is_expert: bool, + game_mode: int, + drunk_world: bool, + get_good_world: bool, created_on, styles: Styles, backgrounds: Backgrounds, @@ -54,6 +56,9 @@ class World: tile_entities: typing.List[TileEntity], weighed_pressure_plates: typing.List[WeighedPressurePlate], rooms: typing.List[Room], + pets: Pets, + treetop_variants: TreetopVariants, + saved_ore_tiers: SavedNPCs, unknown_file_format_data: bytes = b"", unknown_world_header_data: bytes = b"", unknown_world_tiles_data: bytes = b"", @@ -94,8 +99,17 @@ class World: 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.game_mode: int = game_mode + """Which mode the game is in. 0=classic, 1=expert, 2=master, 3=journey""" + + self.is_expert: bool = True if self.game_mode==1 else False + """If the world is in expert mode or not, no longer stored in worldfile.""" + + self.drunk_world: bool = drunk_world + """Was this world created with the drunk worldgen seed.""" + + self.get_good_world: bool = get_good_world + """Was this world created with the get good worldgen seed.""" self.created_on = created_on """The date and time this world was created in.""" @@ -166,6 +180,15 @@ class World: self.weighed_pressure_plates: typing.List[WeighedPressurePlate] = weighed_pressure_plates """A list of all Weighed Pressure Plates in the world.""" + self.pets: Pets = pets + """Which pets have bene purchased.""" + + self.treetop_variants: TreetopVariants = treetop_variants + """Treetops variants that can exist in the world.""" + + self.saved_ore_tiers: SavedOreTiers = saved_ore_tiers + """Probably related to drunk wordgen having both types of ores.""" + self.rooms: typing.List[Room] = rooms self.clouds: Clouds = clouds self.cultist_delay: int = cultist_delay @@ -292,8 +315,8 @@ class World: 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.") + if version != Version("1.4.0.4") or relogic != "relogic" or savefile_type != 2: + raise NotImplementedError("This parser can only read Terraria 1.4.0.4 save files.") revision = f.uint4() is_favorite = f.uint8() != 0 @@ -309,14 +332,15 @@ class World: unknown_file_format_data = f.read_until(pointers.world_header) name = f.string() - generator = GeneratorInfo(f.string(), f.uint8()) uuid_ = f.uuid() id_ = f.int4() bounds = f.rect() world_size = Coordinates(y=f.int4(), x=f.int4()) - is_expert = f.bool() + game_mode = f.int4() + drunk_world = f.bool() + get_good_world = f.bool() created_on = f.datetime() world_styles = Styles(moon=MoonStyle(f.uint1()), @@ -415,18 +439,6 @@ class World: bg_desert = f.int1() bg_ocean = f.int1() - backgrounds = Backgrounds(bg_underground_snow=bg_underground_snow, - bg_underground_jungle=bg_underground_jungle, - bg_hell=bg_hell, - bg_forest=bg_forest, - bg_corruption=bg_corruption, - bg_jungle=bg_jungle, - bg_snow=bg_snow, - bg_hallow=bg_hallow, - bg_crimson=bg_crimson, - bg_desert=bg_desert, - bg_ocean=bg_ocean) - clouds = Clouds(bg_cloud=f.int4(), cloud_number=f.int2(), wind_speed=f.single()) angler_today_quest_completed_by_count = f.uint1() @@ -442,6 +454,7 @@ class World: saved_stylist = f.bool() saved_tax_collector = f.bool() + saved_golfer = f.bool() invasion_size_start = f.int4() # ??? invasion = Invasion(delay=invasion_delay, @@ -497,6 +510,49 @@ class World: severity=f.single(), intended_severity=f.single()) + saved_bartender = f.bool() + + old_ones_army = OldOnesArmyTiers(f.bool(), f.bool(), f.bool()) + + # ToDo: Figure out which biomes got new BGs. + # Oasis and Graveyard probably got new backgrounds. + new_bg_1 = f.int1() + new_bg_2 = f.int1() + new_bg_3 = f.int1() # Maybe oasis. + new_bg_4 = f.int1() + new_bg_5 = f.int1() + + backgrounds = Backgrounds(bg_underground_snow=bg_underground_snow, + bg_underground_jungle=bg_underground_jungle, + bg_hell=bg_hell, + bg_forest=bg_forest, + bg_corruption=bg_corruption, + bg_jungle=bg_jungle, + bg_snow=bg_snow, + bg_hallow=bg_hallow, + bg_crimson=bg_crimson, + bg_desert=bg_desert, + bg_ocean=bg_ocean, + new_bg_1=new_bg_1, + new_bg_2=new_bg_2, + new_bg_3=new_bg_3, + new_bg_4=new_bg_4, + new_bg_5=new_bg_5,) + + combat_book_used = f.bool() + + saved_npcs = SavedNPCs(goblin_tinkerer=saved_goblin_tinkerer, + wizard=saved_wizard, + mechanic=saved_mechanic, + angler=saved_angler, + stylist=saved_stylist, + tax_collector=saved_tax_collector, + bartender=saved_bartender, + golfer=saved_golfer, + advanced_combat=combat_book_used) + + lantern_night = LanternEvent(f.int4(), f.bool(), f.bool(), f.bool()) + events = Events(blood_moon=blood_moon, solar_eclipse=eclipse, invasion=invasion, @@ -504,18 +560,18 @@ class World: rain=rain, party=party, sandstorm=sandstorm, - lunar_events=lunar_events) + lunar_events=lunar_events, + lantern_night=lantern_night) - saved_bartender = f.bool() - saved_npcs = SavedNPCs(goblin_tinkerer=saved_goblin_tinkerer, - wizard=saved_wizard, - mechanic=saved_mechanic, - angler=saved_angler, - stylist=saved_stylist, - tax_collector=saved_tax_collector, - bartender=saved_bartender) + treetop_variant_count = f.int4() + treetop_variants = TreetopVariants([f.int4() for _ in range(treetop_variant_count)]) - old_ones_army = OldOnesArmyTiers(f.bool(), f.bool(), f.bool()) + saved_ore_tiers = SavedOreTiers(f.int4(), f.int4(), f.int4(), f.int4()) + + pets = Pets(f.bool(), f.bool(), f.bool()) + + defeated_empress_of_light = f.bool() + defeated_queen_slime = f.bool() bosses_defeated = BossesDefeated(eye_of_cthulhu=defeated_eye_of_cthulhu, eater_of_worlds=defeated_eater_of_worlds, @@ -542,7 +598,9 @@ class World: lunar_pillars=defeated_pillars, old_ones_army=old_ones_army, martian_madness=defeated_martian_madness, - lunatic_cultist=defeated_lunatic_cultist) + lunatic_cultist=defeated_lunatic_cultist, + empress_of_light=defeated_empress_of_light, + queen_slime=defeated_queen_slime) unknown_world_header_data = f.read_until(pointers.world_tiles) @@ -671,7 +729,8 @@ class World: # Object creation world = cls(version=version, savefile_type=savefile_type, revision=revision, is_favorite=is_favorite, name=name, generator=generator, uuid_=uuid_, id_=id_, bounds=bounds, size=world_size, - is_expert=is_expert, created_on=created_on, styles=world_styles, backgrounds=backgrounds, + game_mode=game_mode, drunk_world=drunk_world, get_good_world=get_good_world, + created_on=created_on, styles=world_styles, backgrounds=backgrounds, spawn_point=spawn_point, underground_level=underground_level, cavern_level=cavern_level, time=time, events=events, dungeon_point=dungeon_point, world_evil=world_evil, saved_npcs=saved_npcs, altars_smashed=altars_smashed, is_hardmode=is_hardmode, @@ -679,6 +738,7 @@ class World: 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, + treetop_variants=treetop_variants, saved_ore_tiers=saved_ore_tiers, pets=pets, unknown_file_format_data=unknown_file_format_data, unknown_world_header_data=unknown_world_header_data, unknown_world_tiles_data=unknown_world_tiles_data,