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

First pass at 1.4.0.4 world header parsing.

This commit is contained in:
Dale Floer 2020-05-29 23:37:53 -07:00 committed by Stefano Pigozzi
parent 835a79d215
commit 06562b0572
12 changed files with 213 additions and 39 deletions

View file

@ -1,5 +1,5 @@
import lihzahrd import lihzahrd
world = lihzahrd.World.create_from_file("Casarao.wld") world = lihzahrd.World.create_from_file("test-1404.wld")
breakpoint() breakpoint()

View file

@ -10,18 +10,22 @@ from .generatorinfo import GeneratorInfo
from .hardmodeore import HardmodeTier1Ore, HardmodeTier2Ore, HardmodeTier3Ore from .hardmodeore import HardmodeTier1Ore, HardmodeTier2Ore, HardmodeTier3Ore
from .invasion import Invasion from .invasion import Invasion
from .invasiontype import InvasionType from .invasiontype import InvasionType
from .lanternevent import LanternEvent
from .lunarevents import LunarEvents from .lunarevents import LunarEvents
from .moonphase import MoonPhase from .moonphase import MoonPhase
from .moonstyle import MoonStyle from .moonstyle import MoonStyle
from .oldonesarmytiers import OldOnesArmyTiers from .oldonesarmytiers import OldOnesArmyTiers
from .party import Party from .party import Party
from .pets import Pets
from .pillarsinfo import PillarsInfo from .pillarsinfo import PillarsInfo
from .rain import Rain from .rain import Rain
from .sandstorm import Sandstorm from .sandstorm import Sandstorm
from .savednpcs import SavedNPCs from .savednpcs import SavedNPCs
from .savedoretiers import SavedOreTiers
from .shadoworbs import ShadowOrbs from .shadoworbs import ShadowOrbs
from .styles import Styles from .styles import Styles
from .time import Time from .time import Time
from .treetopvariants import TreetopVariants
from .version import Version from .version import Version
from .worldeviltype import WorldEvilType from .worldeviltype import WorldEvilType
@ -40,18 +44,22 @@ __all__ = [
"HardmodeTier3Ore", "HardmodeTier3Ore",
"Invasion", "Invasion",
"InvasionType", "InvasionType",
"LanternEvent",
"LunarEvents", "LunarEvents",
"MoonPhase", "MoonPhase",
"MoonStyle", "MoonStyle",
"OldOnesArmyTiers", "OldOnesArmyTiers",
"Party", "Party",
"Pets",
"PillarsInfo", "PillarsInfo",
"Rain", "Rain",
"Sandstorm", "Sandstorm",
"SavedNPCs", "SavedNPCs",
"SavedOreTiers",
"ShadowOrbs", "ShadowOrbs",
"Styles", "Styles",
"Time", "Time",
"TreetopVariants",
"Version", "Version",
"WorldEvilType" "WorldEvilType"
] ]

View file

@ -11,7 +11,12 @@ class Backgrounds:
bg_hallow: int, bg_hallow: int,
bg_crimson: int, bg_crimson: int,
bg_desert: 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_snow: int = bg_underground_snow
self.bg_underground_jungle: int = bg_underground_jungle self.bg_underground_jungle: int = bg_underground_jungle
self.bg_hell: int = bg_hell self.bg_hell: int = bg_hell
@ -23,8 +28,14 @@ class Backgrounds:
self.bg_crimson: int = bg_crimson self.bg_crimson: int = bg_crimson
self.bg_desert: int = bg_desert self.bg_desert: int = bg_desert
self.bg_ocean: int = bg_ocean 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): def __repr__(self):
return f"WorldBackgrounds({self.bg_underground_snow}, {self.bg_underground_jungle}, {self.bg_hell}," \ 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_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})"

View file

@ -29,7 +29,9 @@ class BossesDefeated:
martian_madness: bool, martian_madness: bool,
lunatic_cultist: bool, lunatic_cultist: bool,
lunar_pillars: PillarsInfo, 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.eye_of_cthulhu: bool = eye_of_cthulhu
self.eater_of_worlds: bool = eater_of_worlds self.eater_of_worlds: bool = eater_of_worlds
self.skeletron: bool = skeletron self.skeletron: bool = skeletron
@ -59,6 +61,8 @@ class BossesDefeated:
self.lunatic_cultist: bool = lunatic_cultist self.lunatic_cultist: bool = lunatic_cultist
self.lunar_pillars: PillarsInfo = lunar_pillars self.lunar_pillars: PillarsInfo = lunar_pillars
self.old_ones_army: OldOnesArmyTiers = old_ones_army 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): def __repr__(self):
return f"<BossesDefeated>" return f"<BossesDefeated>"

View file

@ -3,7 +3,7 @@ from .rain import Rain
from .party import Party from .party import Party
from .sandstorm import Sandstorm from .sandstorm import Sandstorm
from .lunarevents import LunarEvents from .lunarevents import LunarEvents
from .lanternevent import LanternEvent
class Events: class Events:
"""Information about the ongoing world events.""" """Information about the ongoing world events."""
@ -15,7 +15,8 @@ class Events:
rain: Rain, rain: Rain,
party: Party, party: Party,
sandstorm: Sandstorm, sandstorm: Sandstorm,
lunar_events: LunarEvents): lunar_events: LunarEvents,
lantern_night: LanternEvent):
self.blood_moon: bool = blood_moon self.blood_moon: bool = blood_moon
"""If the current moon is a Blood Moon.""" """If the current moon is a Blood Moon."""
@ -40,5 +41,8 @@ class Events:
self.lunar_events: LunarEvents = lunar_events self.lunar_events: LunarEvents = lunar_events
"""Information about the currently ongoing 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): def __repr__(self):
return f"<WorldEvents>" return f"<WorldEvents>"

29
lihzahrd/header/lanternevent.py Executable file
View file

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

View file

@ -6,6 +6,13 @@ class MoonStyle(enum.IntEnum):
WHITE = 0 WHITE = 0
ORANGE = 1 ORANGE = 1
RINGED_GREEN = 2 RINGED_GREEN = 2
BLUE = 3
ICE = 4
GREEN = 5
PINK = 6
PINK_ORANGE = 7
TRIPLE_PURPLE = 8
def __repr__(self): def __repr__(self):
return f"{self.__class__.__name__}.{self.name}" return f"{self.__class__.__name__}.{self.name}"

19
lihzahrd/header/pets.py Executable file
View file

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

View file

@ -6,7 +6,9 @@ class SavedNPCs:
angler: bool, angler: bool,
stylist: bool, stylist: bool,
tax_collector: bool, tax_collector: bool,
bartender: bool): bartender: bool,
golfer: bool,
advanced_combat: bool):
self.goblin_tinkerer: bool = goblin_tinkerer self.goblin_tinkerer: bool = goblin_tinkerer
self.wizard: bool = wizard self.wizard: bool = wizard
self.mechanic: bool = mechanic self.mechanic: bool = mechanic
@ -14,8 +16,11 @@ class SavedNPCs:
self.stylist: bool = stylist self.stylist: bool = stylist
self.tax_collector: bool = tax_collector self.tax_collector: bool = tax_collector
self.bartender: bool = bartender self.bartender: bool = bartender
self.golfer: bool = golfer
self.advanced_combat: bool = advanced_combat
"""Was the Advanced Combat Technique Book used."""
def __repr__(self): def __repr__(self):
return f"SavedNPCs(goblin_tinkerer={self.goblin_tinkerer}, wizard={self.wizard}, mechanic={self.mechanic}," \ 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" 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}"

View file

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

View file

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

View file

@ -27,7 +27,9 @@ class World:
id_: int, id_: int,
bounds: Rect, bounds: Rect,
size: Coordinates, size: Coordinates,
is_expert: bool, game_mode: int,
drunk_world: bool,
get_good_world: bool,
created_on, created_on,
styles: Styles, styles: Styles,
backgrounds: Backgrounds, backgrounds: Backgrounds,
@ -54,6 +56,9 @@ class World:
tile_entities: typing.List[TileEntity], tile_entities: typing.List[TileEntity],
weighed_pressure_plates: typing.List[WeighedPressurePlate], weighed_pressure_plates: typing.List[WeighedPressurePlate],
rooms: typing.List[Room], rooms: typing.List[Room],
pets: Pets,
treetop_variants: TreetopVariants,
saved_ore_tiers: SavedNPCs,
unknown_file_format_data: bytes = b"", unknown_file_format_data: bytes = b"",
unknown_world_header_data: bytes = b"", unknown_world_header_data: bytes = b"",
unknown_world_tiles_data: bytes = b"", unknown_world_tiles_data: bytes = b"",
@ -94,8 +99,17 @@ class World:
self.size: Coordinates = size self.size: Coordinates = size
"""The world size in tiles.""" """The world size in tiles."""
self.is_expert: bool = is_expert self.game_mode: int = game_mode
"""If the world is in expert mode or not.""" """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 self.created_on = created_on
"""The date and time this world was created in.""" """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 self.weighed_pressure_plates: typing.List[WeighedPressurePlate] = weighed_pressure_plates
"""A list of all Weighed Pressure Plates in the world.""" """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.rooms: typing.List[Room] = rooms
self.clouds: Clouds = clouds self.clouds: Clouds = clouds
self.cultist_delay: int = cultist_delay self.cultist_delay: int = cultist_delay
@ -292,8 +315,8 @@ class World:
version = Version(f.int4()) version = Version(f.int4())
relogic = f.string(7) relogic = f.string(7)
savefile_type = f.uint1() savefile_type = f.uint1()
if version != Version("1.3.5.3") or relogic != "relogic" or savefile_type != 2: if version != Version("1.4.0.4") or relogic != "relogic" or savefile_type != 2:
raise NotImplementedError("This parser can only read Terraria 1.3.5.3 save files.") raise NotImplementedError("This parser can only read Terraria 1.4.0.4 save files.")
revision = f.uint4() revision = f.uint4()
is_favorite = f.uint8() != 0 is_favorite = f.uint8() != 0
@ -309,14 +332,15 @@ class World:
unknown_file_format_data = f.read_until(pointers.world_header) unknown_file_format_data = f.read_until(pointers.world_header)
name = f.string() name = f.string()
generator = GeneratorInfo(f.string(), f.uint8()) generator = GeneratorInfo(f.string(), f.uint8())
uuid_ = f.uuid() uuid_ = f.uuid()
id_ = f.int4() id_ = f.int4()
bounds = f.rect() bounds = f.rect()
world_size = Coordinates(y=f.int4(), x=f.int4()) 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() created_on = f.datetime()
world_styles = Styles(moon=MoonStyle(f.uint1()), world_styles = Styles(moon=MoonStyle(f.uint1()),
@ -415,18 +439,6 @@ class World:
bg_desert = f.int1() bg_desert = f.int1()
bg_ocean = 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()) clouds = Clouds(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_count = f.uint1()
@ -442,6 +454,7 @@ class World:
saved_stylist = f.bool() saved_stylist = f.bool()
saved_tax_collector = f.bool() saved_tax_collector = f.bool()
saved_golfer = f.bool()
invasion_size_start = f.int4() # ??? invasion_size_start = f.int4() # ???
invasion = Invasion(delay=invasion_delay, invasion = Invasion(delay=invasion_delay,
@ -497,6 +510,49 @@ class World:
severity=f.single(), severity=f.single(),
intended_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, events = Events(blood_moon=blood_moon,
solar_eclipse=eclipse, solar_eclipse=eclipse,
invasion=invasion, invasion=invasion,
@ -504,18 +560,18 @@ class World:
rain=rain, rain=rain,
party=party, party=party,
sandstorm=sandstorm, sandstorm=sandstorm,
lunar_events=lunar_events) lunar_events=lunar_events,
lantern_night=lantern_night)
saved_bartender = f.bool() treetop_variant_count = f.int4()
saved_npcs = SavedNPCs(goblin_tinkerer=saved_goblin_tinkerer, treetop_variants = TreetopVariants([f.int4() for _ in range(treetop_variant_count)])
wizard=saved_wizard,
mechanic=saved_mechanic,
angler=saved_angler,
stylist=saved_stylist,
tax_collector=saved_tax_collector,
bartender=saved_bartender)
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, bosses_defeated = BossesDefeated(eye_of_cthulhu=defeated_eye_of_cthulhu,
eater_of_worlds=defeated_eater_of_worlds, eater_of_worlds=defeated_eater_of_worlds,
@ -542,7 +598,9 @@ class World:
lunar_pillars=defeated_pillars, lunar_pillars=defeated_pillars,
old_ones_army=old_ones_army, old_ones_army=old_ones_army,
martian_madness=defeated_martian_madness, 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) unknown_world_header_data = f.read_until(pointers.world_tiles)
@ -671,7 +729,8 @@ class World:
# Object creation # Object creation
world = cls(version=version, savefile_type=savefile_type, revision=revision, is_favorite=is_favorite, 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, 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, spawn_point=spawn_point, underground_level=underground_level, cavern_level=cavern_level,
time=time, events=events, dungeon_point=dungeon_point, world_evil=world_evil, time=time, events=events, dungeon_point=dungeon_point, world_evil=world_evil,
saved_npcs=saved_npcs, altars_smashed=altars_smashed, is_hardmode=is_hardmode, 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, clouds=clouds, cultist_delay=cultist_delay, tiles=tm, chests=chests, signs=signs,
npcs=npcs, mobs=mobs, tile_entities=tile_entities, npcs=npcs, mobs=mobs, tile_entities=tile_entities,
weighed_pressure_plates=weighed_pressure_plates, rooms=rooms, 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_file_format_data=unknown_file_format_data,
unknown_world_header_data=unknown_world_header_data, unknown_world_header_data=unknown_world_header_data,
unknown_world_tiles_data=unknown_world_tiles_data, unknown_world_tiles_data=unknown_world_tiles_data,