mirror of
https://github.com/Steffo99/lihzahrd.git
synced 2024-11-21 23:54:23 +00:00
774 lines
30 KiB
Python
774 lines
30 KiB
Python
import uuid
|
|
import math
|
|
import typing
|
|
from .fileutils import *
|
|
from .header import *
|
|
from .tiles import *
|
|
from .chests import *
|
|
from .signs import *
|
|
from .npcs import *
|
|
from .tileentities import *
|
|
from .pressureplates import *
|
|
from .townmanager import *
|
|
from .errors import InvalidFooterError
|
|
|
|
|
|
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,
|
|
game_mode: int,
|
|
drunk_world: bool,
|
|
get_good_world: bool,
|
|
created_on,
|
|
styles: Styles,
|
|
backgrounds: Backgrounds,
|
|
spawn_point: Coordinates,
|
|
underground_level: float,
|
|
cavern_level: float,
|
|
time: Time,
|
|
events: Events,
|
|
dungeon_point: Coordinates,
|
|
world_evil: WorldEvilType,
|
|
saved_npcs: SavedNPCs,
|
|
altars_smashed: AltarsSmashed,
|
|
is_hardmode: bool,
|
|
shadow_orbs: ShadowOrbs,
|
|
bosses_defeated: BossesDefeated,
|
|
anglers_quest: AnglerQuest,
|
|
clouds: Clouds,
|
|
cultist_delay: int,
|
|
tiles: TileMatrix,
|
|
chests: typing.List[Chest],
|
|
signs: typing.List[Sign],
|
|
npcs: typing.List[NPC],
|
|
mobs: typing.List[Mob],
|
|
tile_entities: typing.List[TileEntity],
|
|
weighed_pressure_plates: typing.List[WeighedPressurePlate],
|
|
rooms: typing.List[Room],
|
|
pets: Pets,
|
|
halloween_today: bool,
|
|
xmas_today: bool,
|
|
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"",
|
|
unknown_chests_data: bytes = b"",
|
|
unknown_signs_data: bytes = b"",
|
|
unknown_npcs_data: bytes = b"",
|
|
unknown_tile_entities_data: bytes = b"",
|
|
unknown_pressure_plates_data: bytes = b"",
|
|
unknown_town_manager_data: bytes = b""):
|
|
|
|
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.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."""
|
|
|
|
self.styles: Styles = styles
|
|
"""The styles of various world elements."""
|
|
|
|
self.backgrounds: Backgrounds = backgrounds
|
|
"""The backgrounds of the various biomes."""
|
|
|
|
self.spawn_point: Coordinates = spawn_point
|
|
"""The coordinates of the spawn point."""
|
|
|
|
self.underground_level: float = underground_level
|
|
"""The depth at which the underground biome starts."""
|
|
|
|
self.cavern_level: float = cavern_level
|
|
"""The depth at which the cavern biome starts."""
|
|
|
|
self.time: Time = time
|
|
"""Game time related information."""
|
|
|
|
self.events: Events = events
|
|
"""Currently ongoing world events."""
|
|
|
|
self.dungeon_point: Coordinates = dungeon_point
|
|
"""The Old Man spawn point."""
|
|
|
|
self.world_evil: WorldEvilType = world_evil
|
|
"""Whether the world has Corruption or Crimson."""
|
|
|
|
self.saved_npcs: SavedNPCs = saved_npcs
|
|
"""The NPCs that were rescued by the player."""
|
|
|
|
self.altars_smashed: AltarsSmashed = altars_smashed
|
|
"""Information related to the destruction of Demon Altars with a Pwnhammer."""
|
|
|
|
self.is_hardmode: bool = is_hardmode
|
|
"""Whether or not the world is in hardmode."""
|
|
|
|
self.shadow_orbs: ShadowOrbs = shadow_orbs
|
|
"""Information related to the Shadow Orbs or Crimson Hearts in the world."""
|
|
|
|
self.bosses_defeated: BossesDefeated = bosses_defeated
|
|
"""Which bosses have been defeated in the world."""
|
|
|
|
self.anglers_quest: AnglerQuest = anglers_quest
|
|
"""Information about today's Angler's Quest."""
|
|
|
|
self.tiles: TileMatrix = tiles
|
|
"""A matrix of all the tiles present in the world."""
|
|
|
|
self.chests: typing.List[Chest] = chests
|
|
"""A list of all the containers (chests, barrels) in the world."""
|
|
|
|
self.signs: typing.List[Sign] = signs
|
|
"""A list of all non-empty signs in the world."""
|
|
|
|
self.npcs: typing.List[NPC] = npcs
|
|
"""A list of all the NPCs currently living in the world, including the Old Man."""
|
|
|
|
self.mobs: typing.List[Mob] = mobs
|
|
"""A list of mobs in the world...?"""
|
|
|
|
self.tile_entities: typing.List[TileEntity] = tile_entities
|
|
"""A list of tile entities in the world, such as Training Dummies, Item Frames and Logic Sensors."""
|
|
|
|
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.halloween_today: bool = halloween_today
|
|
"""Is today an halloween reward day?"""
|
|
|
|
self.xmas_today: bool = xmas_today
|
|
"""Is today an xmas reward day?"""
|
|
|
|
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
|
|
self.unknown_file_format_data: bytes = unknown_file_format_data
|
|
self.unknown_world_header_data: bytes = unknown_world_header_data
|
|
self.unknown_world_tiles_data: bytes = unknown_world_tiles_data
|
|
self.unknown_chests_data: bytes = unknown_chests_data
|
|
self.unknown_signs_data: bytes = unknown_signs_data
|
|
self.unknown_npcs_data: bytes = unknown_npcs_data
|
|
self.unknown_tile_entities_data: bytes = unknown_tile_entities_data
|
|
self.unknown_pressure_plates_data: bytes = unknown_pressure_plates_data
|
|
self.unknown_town_manager_data: bytes = unknown_town_manager_data
|
|
|
|
def __repr__(self):
|
|
return f'<World "{self.name}">'
|
|
|
|
@property
|
|
def crimson_hearts(self) -> ShadowOrbs:
|
|
"""Information related to the Shadow Orbs or Crimson Hearts in the world."""
|
|
return self.shadow_orbs
|
|
|
|
@crimson_hearts.setter
|
|
def crimson_hearts(self, value):
|
|
self.shadow_orbs = value
|
|
|
|
@staticmethod
|
|
def _read_tile_block(fr: FileReader, tileframeimportant) -> typing.List:
|
|
# Once again, this code is a mess
|
|
flags1 = fr.bits()
|
|
has_block = flags1[1]
|
|
has_wall = flags1[2]
|
|
liquid_type = LiquidType.from_flags(flags1)
|
|
has_extended_block_id = flags1[5]
|
|
rle_compression = RLEEncoding.from_flags(flags1)
|
|
if flags1[0]:
|
|
flags2 = fr.bits()
|
|
block_shape = Shape.from_flags(flags2)
|
|
if flags2[0]:
|
|
flags3 = fr.bits()
|
|
is_block_active = not flags3[2]
|
|
wiring = Wiring.from_flags(flags2, flags3)
|
|
is_block_painted = flags3[3]
|
|
is_wall_painted = flags3[4]
|
|
else:
|
|
is_block_active = True
|
|
wiring = Wiring.from_flags(flags2)
|
|
is_block_painted = False
|
|
is_wall_painted = False
|
|
else:
|
|
block_shape = Shape.NORMAL
|
|
is_block_active = True
|
|
wiring = None
|
|
is_block_painted = False
|
|
is_wall_painted = False
|
|
if has_block:
|
|
if has_extended_block_id:
|
|
block_type = BlockType(fr.uint2())
|
|
else:
|
|
block_type = BlockType(fr.uint1())
|
|
if tileframeimportant[block_type]:
|
|
frame = FrameImportantData(fr.uint2(), fr.uint2())
|
|
else:
|
|
frame = None
|
|
if is_block_painted:
|
|
block_paint = fr.uint1()
|
|
else:
|
|
block_paint = None
|
|
block = Block(type_=block_type,
|
|
frame=frame,
|
|
paint=block_paint,
|
|
is_active=is_block_active,
|
|
shape=block_shape)
|
|
else:
|
|
block = None
|
|
if has_wall:
|
|
wall_id = WallType(fr.uint1())
|
|
if is_wall_painted:
|
|
wall_paint = fr.uint1()
|
|
else:
|
|
wall_paint = None
|
|
wall = Wall(type_=wall_id, paint=wall_paint)
|
|
else:
|
|
wall = None
|
|
if liquid_type != LiquidType.NO_LIQUID:
|
|
liquid = Liquid(type_=liquid_type, volume=fr.uint1())
|
|
else:
|
|
liquid = None
|
|
if rle_compression == RLEEncoding.DOUBLE_BYTE:
|
|
multiply_by = fr.uint2() + 1
|
|
elif rle_compression == RLEEncoding.SINGLE_BYTE:
|
|
multiply_by = fr.uint1() + 1
|
|
else:
|
|
multiply_by = 1
|
|
tile = Tile(block=block, wall=wall, liquid=liquid, wiring=wiring)
|
|
return [tile] * multiply_by
|
|
|
|
@classmethod
|
|
def _create_tilematrix(cls, f, world_size: Coordinates, tileframeimportant: typing.List[bool]):
|
|
"""Create a TileMatrix object from a file."""
|
|
tm = TileMatrix()
|
|
while tm.size.x < world_size.x:
|
|
column = []
|
|
while len(column) < world_size.y:
|
|
readtiles = cls._read_tile_block(f, tileframeimportant)
|
|
column = [*column, *readtiles]
|
|
tm.add_column(column)
|
|
return tm
|
|
|
|
@classmethod
|
|
def create_from_file(cls, filename: str):
|
|
"""Create a World object from a .wld file.
|
|
|
|
Warning:
|
|
Parsing an entire world may take up to a few minutes and quite a bit of memory!
|
|
|
|
Arguments:
|
|
filename: The name of the file that should be parsed."""
|
|
# This code is a mess.
|
|
|
|
file = open(filename, "rb")
|
|
f = FileReader(file)
|
|
|
|
# File header
|
|
version = Version(f.int4())
|
|
relogic = f.string(7)
|
|
savefile_type = f.uint1()
|
|
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
|
|
|
|
# Pointers and tileframeimportant
|
|
pointers = Pointers(*[f.int4() for _ in range(f.int2())])
|
|
tileframeimportant_size = math.ceil(f.int2() / 8)
|
|
tileframeimportant = []
|
|
for _ in range(tileframeimportant_size):
|
|
current_bit = f.bits()
|
|
tileframeimportant = [*tileframeimportant, *current_bit]
|
|
|
|
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())
|
|
game_mode = f.int4()
|
|
drunk_world = f.bool()
|
|
get_good_world = f.bool()
|
|
created_on = f.datetime()
|
|
|
|
world_styles = Styles(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 = Coordinates(f.int4(), f.int4())
|
|
underground_level = f.double()
|
|
cavern_level = f.double()
|
|
|
|
current_time = f.double()
|
|
is_daytime = f.bool()
|
|
moon_phase = MoonPhase(f.uint4())
|
|
|
|
blood_moon = f.bool()
|
|
eclipse = f.bool()
|
|
|
|
dungeon_point = Coordinates(f.int4(), f.int4())
|
|
world_evil = WorldEvilType(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()
|
|
|
|
shadow_orbs = ShadowOrbs(smashed_at_least_once=f.bool(),
|
|
spawn_meteorite=f.bool(),
|
|
evil_boss_counter=f.int4())
|
|
|
|
smashed_altars_count = f.int4()
|
|
|
|
is_hardmode = f.bool()
|
|
|
|
invasion_delay = f.int4()
|
|
invasion_size = f.int4()
|
|
invasion_type = InvasionType(f.int4())
|
|
invasion_position = f.double()
|
|
|
|
time_left_slime_rain = f.double()
|
|
|
|
sundial_cooldown = f.uint1()
|
|
|
|
rain = Rain(is_active=f.bool(), time_left=f.int4(), max_rain=f.single())
|
|
|
|
try:
|
|
hardmode_ore_1 = HardmodeTier1Ore(f.int4())
|
|
except ValueError:
|
|
hardmode_ore_1 = HardmodeTier1Ore.NOT_DETERMINED
|
|
try:
|
|
hardmode_ore_2 = HardmodeTier2Ore(f.int4())
|
|
except ValueError:
|
|
hardmode_ore_2 = HardmodeTier2Ore.NOT_DETERMINED
|
|
try:
|
|
hardmode_ore_3 = HardmodeTier3Ore(f.int4())
|
|
except ValueError:
|
|
hardmode_ore_3 = HardmodeTier3Ore.NOT_DETERMINED
|
|
altars_smashed = AltarsSmashed(count=smashed_altars_count,
|
|
ore_tier1=hardmode_ore_1,
|
|
ore_tier2=hardmode_ore_2,
|
|
ore_tier3=hardmode_ore_3)
|
|
|
|
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()
|
|
|
|
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 = []
|
|
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 = AnglerQuestFish(f.int4())
|
|
anglers_quest = AnglerQuest(current_goal=angler_today_quest_target,
|
|
completed_by=angler_today_quest_completed_by)
|
|
|
|
saved_stylist = f.bool()
|
|
saved_tax_collector = f.bool()
|
|
saved_golfer = f.bool()
|
|
|
|
invasion_size_start = f.int4() # ???
|
|
invasion = Invasion(delay=invasion_delay,
|
|
size=invasion_size,
|
|
type_=invasion_type,
|
|
position=invasion_position,
|
|
size_start=invasion_size_start)
|
|
|
|
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()
|
|
time = Time(current=current_time,
|
|
is_daytime=is_daytime,
|
|
moon_phase=moon_phase,
|
|
sundial_cooldown=sundial_cooldown,
|
|
fast_forward_time=fast_forward_time)
|
|
|
|
defeated_duke_fishron = f.bool()
|
|
defeated_martian_madness = f.bool()
|
|
defeated_lunatic_cultist = 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_pillars = PillarsInfo(solar=f.bool(), vortex=f.bool(), nebula=f.bool(), stardust=f.bool())
|
|
|
|
lunar_events = LunarEvents(pillars_present=PillarsInfo(solar=f.bool(),
|
|
vortex=f.bool(),
|
|
nebula=f.bool(),
|
|
stardust=f.bool()),
|
|
are_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())
|
|
party = Party(thrown_by_party_center=party_center_active,
|
|
thrown_by_npcs=party_natural_active,
|
|
cooldown=party_cooldown,
|
|
partying_npcs=partying_npcs)
|
|
|
|
sandstorm = Sandstorm(is_active=f.bool(),
|
|
time_left=f.int4(),
|
|
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,
|
|
slime_rain=time_left_slime_rain,
|
|
rain=rain,
|
|
party=party,
|
|
sandstorm=sandstorm,
|
|
lunar_events=lunar_events,
|
|
lantern_night=lantern_night)
|
|
|
|
treetop_variant_count = f.int4()
|
|
treetop_variants = TreetopVariants([f.int4() for _ in range(treetop_variant_count)])
|
|
|
|
halloween_today = f.bool()
|
|
xmas_today = 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,
|
|
skeletron=defeated_skeletron,
|
|
queen_bee=defeated_queen_bee,
|
|
the_twins=defeated_the_twins,
|
|
the_destroyer=defeated_the_destroyer,
|
|
skeletron_prime=defeated_skeletron_prime,
|
|
any_mechnical_boss=defeated_any_mechnical_boss,
|
|
plantera=defeated_plantera,
|
|
golem=defeated_golem,
|
|
king_slime=defeated_king_slime,
|
|
goblin_army=defeated_goblin_army,
|
|
clown=defeated_clown,
|
|
frost_moon=defeated_frost_moon,
|
|
pirates=defeated_pirates,
|
|
duke_fishron=defeated_duke_fishron,
|
|
moon_lord=defeated_moon_lord,
|
|
pumpking=defeated_pumpking,
|
|
mourning_wood=defeated_mourning_wood,
|
|
ice_queen=defeated_ice_queen,
|
|
santa_nk1=defeated_santa_nk1,
|
|
everscream=defeated_everscream,
|
|
lunar_pillars=defeated_pillars,
|
|
old_ones_army=old_ones_army,
|
|
martian_madness=defeated_martian_madness,
|
|
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)
|
|
|
|
# Tiles
|
|
tm = cls._create_tilematrix(f, world_size, tileframeimportant)
|
|
|
|
unknown_world_tiles_data = f.read_until(pointers.chests)
|
|
|
|
# Chests
|
|
chests = []
|
|
|
|
chests_count = f.int2()
|
|
chests_max_items = f.int2()
|
|
|
|
for _ in range(chests_count):
|
|
chest_position = Coordinates(x=f.int4(), y=f.int4())
|
|
chest_name = f.string()
|
|
chest_contents = []
|
|
|
|
for _ in range(chests_max_items):
|
|
item_quantity = f.int2()
|
|
if item_quantity > 0:
|
|
item_type = ItemType(f.int4())
|
|
item_modifier = f.uint1()
|
|
item = ItemStack(quantity=item_quantity,
|
|
type_=item_type,
|
|
modifier=item_modifier)
|
|
else:
|
|
item = None
|
|
chest_contents.append(item)
|
|
chest = Chest(position=chest_position,
|
|
name=chest_name,
|
|
contents=chest_contents)
|
|
chests.append(chest)
|
|
tm[chest.position].extra = chest
|
|
|
|
unknown_chests_data = f.read_until(pointers.signs)
|
|
|
|
# Signs
|
|
signs = []
|
|
|
|
signs_count = f.int2()
|
|
|
|
for _ in range(signs_count):
|
|
sign = Sign(text=f.string(),
|
|
position=Coordinates(f.int4(), f.int4()))
|
|
signs.append(sign)
|
|
tm[sign.position].extra = sign
|
|
|
|
unknown_signs_data = f.read_until(pointers.npcs)
|
|
|
|
# Entities
|
|
npcs = []
|
|
mobs = []
|
|
|
|
while f.bool():
|
|
npc_type = EntityType(f.int4())
|
|
npc_name = f.string()
|
|
npc_position = Coordinates(f.single(), f.single())
|
|
is_homeless = f.bool()
|
|
npc_home = Coordinates(f.int4(), f.int4())
|
|
if is_homeless:
|
|
npc_home = None
|
|
|
|
npc = NPC(type_=npc_type,
|
|
name=npc_name,
|
|
position=npc_position,
|
|
home=npc_home)
|
|
npcs.append(npc)
|
|
|
|
while f.bool():
|
|
mob_type = EntityType(f.int4())
|
|
mob_position = Coordinates(f.single(), f.single())
|
|
|
|
mob = Mob(type_=mob_type,
|
|
position=mob_position)
|
|
mobs.append(mob)
|
|
|
|
unknown_npcs_data = f.read_until(pointers.tile_entities)
|
|
|
|
# Tile entities
|
|
tile_entities_count = f.int4()
|
|
tile_entities = []
|
|
|
|
for _ in range(tile_entities_count):
|
|
te_type = f.uint1()
|
|
te_id = f.int4()
|
|
te_position = Coordinates(f.int2(), f.int2())
|
|
if te_type == 0:
|
|
te_extra = TargetDummy(npc=f.int2())
|
|
elif te_type == 1:
|
|
te_extra = ItemFrame(item=ItemStack(type_=ItemType(f.int2()),
|
|
modifier=f.uint1(),
|
|
quantity=f.int2()))
|
|
elif te_type == 2:
|
|
te_extra = LogicSensor(logic_check=f.uint1(), enabled=f.bool())
|
|
else:
|
|
te_extra = ...
|
|
tile_entity = TileEntity(id_=te_id, position=te_position, extra=te_extra)
|
|
tile_entities.append(tile_entity)
|
|
tm[tile_entity.position].extra = tile_entity
|
|
|
|
unknown_tile_entities_data = f.read_until(pointers.pressure_plates)
|
|
|
|
# Weighed Pressure Plates
|
|
weighed_pressure_plates_count = f.int4()
|
|
weighed_pressure_plates = []
|
|
|
|
for _ in range(weighed_pressure_plates_count):
|
|
wpp = WeighedPressurePlate(position=Coordinates(f.int4(), f.int4()))
|
|
weighed_pressure_plates.append(wpp)
|
|
tm[wpp.position].extra = wpp
|
|
|
|
unknown_pressure_plates_data = f.read_until(pointers.town_manager)
|
|
|
|
# Town Manager
|
|
rooms_count = f.int4()
|
|
rooms = []
|
|
|
|
for _ in range(rooms_count):
|
|
room = Room(npc=EntityType(f.int4()), position=Coordinates(f.int4(), f.int4()))
|
|
rooms.append(room)
|
|
|
|
unknown_town_manager_data = f.read_until(pointers.footer)
|
|
|
|
# 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,
|
|
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,
|
|
shadow_orbs=shadow_orbs, bosses_defeated=bosses_defeated, anglers_quest=anglers_quest,
|
|
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,
|
|
halloween_today=halloween_today, xmas_today=xmas_today,
|
|
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,
|
|
unknown_chests_data=unknown_chests_data,
|
|
unknown_signs_data=unknown_signs_data,
|
|
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)
|
|
|
|
# Footer
|
|
if not f.bool():
|
|
raise InvalidFooterError("Invalid footer")
|
|
if not f.string() == world.name:
|
|
raise InvalidFooterError("Invalid footer")
|
|
if not f.int4() == world.id:
|
|
raise InvalidFooterError("Invalid footer")
|
|
|
|
f.file.close()
|
|
|
|
return world
|