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

Start work on tile data

This commit is contained in:
Steffo 2019-08-09 15:16:57 +02:00
parent 67e3627a68
commit 6f8b9b75b3
13 changed files with 278 additions and 155 deletions

View file

@ -1,4 +1,3 @@
from . import header
from .world import World
__all__ = ["header", "World"]
__all__ = ["World"]

View file

@ -6,7 +6,6 @@ from .bossesdefeated import BossesDefeated
from .clouds import Clouds
from .coordinates import Coordinates
from .events import Events
from .filereader import FileReader
from .fourpartsplit import FourPartSplit
from .generatorinfo import GeneratorInfo
from .hardmodeore import HardmodeTier1Ore, HardmodeTier2Ore, HardmodeTier3Ore
@ -19,7 +18,6 @@ from .oldonesarmytiers import OldOnesArmyTiers
from .party import Party
from .pillarsinfo import PillarsInfo
from .rain import Rain
from .rect import Rect
from .sandstorm import Sandstorm
from .savednpcs import SavedNPCs
from .shadoworbs import ShadowOrbs

View file

View file

@ -42,7 +42,7 @@ class FileReader:
def double(self) -> float:
return struct.unpack("d", self.file.read(8))[0]
def bit(self) -> typing.Tuple[bool, bool, bool, bool, bool, bool, bool, bool]:
def bits(self) -> typing.Tuple[bool, bool, bool, bool, bool, bool, bool, bool]:
data = struct.unpack("B", self.file.read(1))[0]
return (bool(data & 0b1000_0000),
bool(data & 0b0100_0000),

View file

@ -0,0 +1,6 @@
from .liquid import Liquid
from .rleencoding import RLEEncoding
from .shape import Shape
from .wires import Wires
__all__ = ["Liquid", "RLEEncoding", "Shape", "Wires"]

18
lihzahrd/tiles/liquid.py Normal file
View file

@ -0,0 +1,18 @@
import enum
class Liquid(enum.IntEnum):
NO_LIQUID = 0
WATER = 1
LAVA = 2
HONEY = 3
@classmethod
def from_flags(cls, flags1):
if flags1[3] and flags1[4]:
return cls.HONEY
if flags1[4]:
return cls.LAVA
if flags1[3]:
return cls.WATER
return cls.NO_LIQUID

View file

@ -0,0 +1,11 @@
import enum
class RLEEncoding(enum.IntEnum):
NO_COMPRESSION = 0
SINGLE_BYTE = 1
DOUBLE_BYTE = 2
@classmethod
def from_flags(cls, flags1):
return cls(flags1[7] * 2 + flags1[6])

14
lihzahrd/tiles/shape.py Normal file
View file

@ -0,0 +1,14 @@
import enum
class Shape(enum.IntEnum):
NORMAL = 0
HALF_TILE = 1
TOP_RIGHT_SLOPE = 2
TOP_LEFT_SLOPE = 3
BOTTOM_RIGHT_SLOPE = 4
BOTTOM_LEFT_SLOPE = 5
@classmethod
def from_flags(cls, flags2):
return cls(flags2[6] * 4 + flags2[5] * 2 + flags2[4])

18
lihzahrd/tiles/tile.py Normal file
View file

@ -0,0 +1,18 @@
import enum
class Liquid(enum.IntEnum):
NO_LIQUID = 0
WATER = 1
LAVA = 2
HONEY = 3
@classmethod
def from_flags(cls, flags13, flags14):
if flags13 and flags14:
return cls.HONEY
if flags14:
return cls.LAVA
if flags13:
return cls.WATER
return cls.NO_LIQUID

12
lihzahrd/tiles/wires.py Normal file
View file

@ -0,0 +1,12 @@
class Wires:
def __init__(self,
red: bool = False,
green: bool = False,
blue: bool = False,
yellow: bool = False,
actuator: bool = False):
self.red: bool = red
self.green: bool = green
self.blue: bool = blue
self.yellow: bool = yellow
self.actuator: bool = actuator

View file

@ -1,42 +1,45 @@
import uuid
import math
from . import header as h
from .header import *
from .savedata import filereader
from .tiles import *
class World:
"""The Python representation of a Terraria world."""
def __init__(self,
version: h.Version,
version: Version,
savefile_type: int,
revision: int,
is_favorite: bool,
name: str,
generator: h.GeneratorInfo,
generator: GeneratorInfo,
uuid_: uuid.UUID,
id_: int,
bounds: h.Rect,
size: h.Coordinates,
bounds: Rect,
size: Coordinates,
is_expert: bool,
created_on,
styles: h.Styles,
backgrounds: h.Backgrounds,
spawn_point: h.Coordinates,
styles: Styles,
backgrounds: Backgrounds,
spawn_point: Coordinates,
underground_level: float,
cavern_level: float,
time: h.Time,
events: h.Events,
dungeon_point: h.Coordinates,
world_evil: h.WorldEvilType,
saved_npcs: h.SavedNPCs,
altars_smashed: h.AltarsSmashed,
time: Time,
events: Events,
dungeon_point: Coordinates,
world_evil: WorldEvilType,
saved_npcs: SavedNPCs,
altars_smashed: AltarsSmashed,
is_hardmode: bool,
shadow_orbs: h.ShadowOrbs,
bosses_defeated: h.BossesDefeated,
anglers_quest: h.AnglerQuest,
clouds: h.Clouds,
shadow_orbs: ShadowOrbs,
bosses_defeated: BossesDefeated,
anglers_quest: AnglerQuest,
clouds: Clouds,
cultist_delay: int):
self.version: h.Version = version
self.version: Version = version
"""The game version when this savefile was last saved."""
self.savefile_type = savefile_type
@ -51,7 +54,7 @@ class World:
self.name: str = name
"""The name the world was given at creation. Doesn't always match the filename."""
self.generator: h.GeneratorInfo = generator
self.generator: GeneratorInfo = generator
"""Information about the generation of this world."""
self.uuid: uuid.UUID = uuid_
@ -60,10 +63,10 @@ class World:
self.id: int = id_
"""The world id. Used to name the minimap file."""
self.bounds: h.rect.Rect = bounds
self.bounds: Rect = bounds
"""The world size in pixels."""
self.size: h.Coordinates = size
self.size: Coordinates = size
"""The world size in tiles."""
self.is_expert: bool = is_expert
@ -72,13 +75,13 @@ class World:
self.created_on = created_on
"""The date and time this world was created in."""
self.styles: h.Styles = styles
self.styles: Styles = styles
"""The styles of various world elements."""
self.backgrounds: h.Backgrounds = backgrounds
self.backgrounds: Backgrounds = backgrounds
"""The backgrounds of the various biomes."""
self.spawn_point: h.Coordinates = spawn_point
self.spawn_point: Coordinates = spawn_point
"""The coordinates of the spawn point."""
self.underground_level: float = underground_level
@ -87,61 +90,100 @@ class World:
self.cavern_level: float = cavern_level
"""The depth at which the cavern biome starts."""
self.time: h.Time = time
self.time: Time = time
"""Game time related information."""
self.events: h.Events = events
self.events: Events = events
"""Currently ongoing world events."""
self.dungeon_point: h.Coordinates = dungeon_point
self.dungeon_point: Coordinates = dungeon_point
"""The Old Man spawn point."""
self.world_evil: h.WorldEvilType = world_evil
self.world_evil: WorldEvilType = world_evil
"""Whether the world has Corruption or Crimson."""
self.saved_npcs: h.SavedNPCs = saved_npcs
self.saved_npcs: SavedNPCs = saved_npcs
"""The NPCs that were rescued by the player."""
self.altars_smashed: h.AltarsSmashed = altars_smashed
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: h.ShadowOrbs = shadow_orbs
self.shadow_orbs: ShadowOrbs = shadow_orbs
"""Information related to the Shadow Orbs or Crimson Hearts in the world."""
self.bosses_defeated: h.BossesDefeated = bosses_defeated
self.bosses_defeated: BossesDefeated = bosses_defeated
"""Which bosses have been defeated in the world."""
self.anglers_quest: h.AnglerQuest = anglers_quest
self.anglers_quest: AnglerQuest = anglers_quest
"""Information about today's Angler's Quest."""
self.clouds: h.Clouds = clouds
self.clouds: Clouds = clouds
self.cultist_delay: int = cultist_delay
def __repr__(self):
return f'<World "{self.name}">'
@property
def crimson_hearts(self) -> h.shadoworbs.ShadowOrbs:
def crimson_hearts(self) -> ShadowOrbs:
return self.shadow_orbs
@crimson_hearts.setter
def crimson_hearts(self, value):
self.shadow_orbs = value
@staticmethod
def _read_tiles(fr: FileReader, tileframeimportant):
flags1 = fr.bits()
has_tile = flags1[1]
has_wall = flags1[2]
liquid = Liquid.from_flags(flags1)
extended_block_id = flags1[5]
rle_compression = RLEEncoding.from_flags(flags1)
if flags1[0]:
flags2 = fr.bits()
shape = Shape.from_flags(flags2)
if flags2[0]:
flags3 = fr.bits()
is_active = not flags3[2]
wires = Wires(red=flags2[1], green=flags2[2], blue=flags2[3], yellow=flags3[5], actuator=flags3[1])
is_tile_painted = flags3[3]
is_wall_painted = flags3[4]
else:
is_active = True
wires = Wires(red=flags2[1], green=flags2[2], blue=flags2[3])
is_tile_painted = False
is_wall_painted = False
else:
shape = Shape.NORMAL
is_active = True
wires = Wires()
is_tile_painted = False
is_wall_painted = False
if has_tile:
if extended_block_id:
block_id = fr.uint2()
else:
block_id = fr.uint1()
else:
block_id = None
if tileframeimportant:
...
breakpoint()
@classmethod
def create_from_file(cls, file):
"""Create a World object from a .wld file."""
# This code is a mess.
f = h.filereader.FileReader(file)
f = filereader.FileReader(file)
version = h.version.Version(f.int4())
version = Version(f.int4())
relogic = f.string(7)
savefile_type = f.uint1()
if version != h.version.Version("1.3.5.3") or relogic != "relogic" or savefile_type != 2:
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()
@ -150,29 +192,29 @@ class World:
# Pointers and tileframeimportant
_ = [f.int4() for _ in range(f.int2())]
tileframeimportant_size = math.ceil(f.int2() / 8)
_ = []
tileframeimportant = []
for _ in range(tileframeimportant_size):
current_bit = f.bit()
_ = [*_, *current_bit]
current_bit = f.bits()
tileframeimportant = [*tileframeimportant, *current_bit]
name = f.string()
generator = h.GeneratorInfo(f.string(), f.int4())
generator = GeneratorInfo(f.string(), f.int4())
uuid_ = f.uuid()
id_ = f.int8()
bounds = f.rect()
world_size = h.Coordinates(y=f.int4(), x=f.int4())
world_size = Coordinates(y=f.int4(), x=f.int4())
is_expert = f.bool()
created_on = f.datetime()
world_styles = h.Styles(moon=h.moonstyle.MoonStyle(f.uint1()),
trees=h.fourpartsplit.FourPartSplit(separators=[f.int4(), f.int4(), f.int4()],
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=h.fourpartsplit.FourPartSplit(separators=[f.int4(), f.int4(), f.int4()],
moss=FourPartSplit(separators=[f.int4(), f.int4(), f.int4()],
properties=[f.int4(),
f.int4(),
f.int4(),
@ -182,19 +224,19 @@ class World:
bg_underground_jungle = f.int4()
bg_hell = f.int4()
spawn_point = h.Coordinates(f.int4(), 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 = h.MoonPhase(f.uint4())
moon_phase = MoonPhase(f.uint4())
blood_moon = f.bool()
eclipse = f.bool()
dungeon_point = h.Coordinates(f.int4(), f.int4())
world_evil = h.WorldEvilType(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.
@ -217,7 +259,7 @@ class World:
defeated_frost_moon = f.bool()
defeated_pirates = f.bool()
shadow_orbs = h.ShadowOrbs(smashed_at_least_once=f.bool(),
shadow_orbs = ShadowOrbs(smashed_at_least_once=f.bool(),
spawn_meteorite=f.bool(),
evil_boss_counter=f.int4())
@ -227,19 +269,19 @@ class World:
invasion_delay = f.int4()
invasion_size = f.int4()
invasion_type = h.InvasionType(f.int4())
invasion_type = InvasionType(f.int4())
invasion_position = f.double()
time_left_slime_rain = f.double()
sundial_cooldown = f.uint1()
rain = h.Rain(is_active=f.bool(), time_left=f.int4(), max_rain=f.single())
rain = Rain(is_active=f.bool(), time_left=f.int4(), max_rain=f.single())
hardmode_ore_1 = h.HardmodeTier1Ore(f.int4())
hardmode_ore_2 = h.HardmodeTier2Ore(f.int4())
hardmode_ore_3 = h.HardmodeTier3Ore(f.int4())
altars_smashed = h.AltarsSmashed(count=smashed_altars_count,
hardmode_ore_1 = HardmodeTier1Ore(f.int4())
hardmode_ore_2 = HardmodeTier2Ore(f.int4())
hardmode_ore_3 = HardmodeTier3Ore(f.int4())
altars_smashed = AltarsSmashed(count=smashed_altars_count,
ore_tier1=hardmode_ore_1,
ore_tier2=hardmode_ore_2,
ore_tier3=hardmode_ore_3)
@ -253,7 +295,7 @@ class World:
bg_desert = f.int1()
bg_ocean = f.int1()
backgrounds = h.Backgrounds(bg_underground_snow=bg_underground_snow,
backgrounds = Backgrounds(bg_underground_snow=bg_underground_snow,
bg_underground_jungle=bg_underground_jungle,
bg_hell=bg_hell,
bg_forest=bg_forest,
@ -265,7 +307,7 @@ class World:
bg_desert=bg_desert,
bg_ocean=bg_ocean)
clouds = h.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 = []
@ -274,15 +316,15 @@ class World:
saved_angler = f.bool()
angler_today_quest_target = h.AnglerQuestFish(f.int4())
anglers_quest = h.AnglerQuest(current_goal=angler_today_quest_target,
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()
invasion_size_start = f.int4() # ???
invasion = h.Invasion(delay=invasion_delay,
invasion = Invasion(delay=invasion_delay,
size=invasion_size,
type_=invasion_type,
position=invasion_position,
@ -295,7 +337,7 @@ class World:
mob_kills[mob_id] = f.int4()
fast_forward_time = f.bool()
time = h.Time(current=current_time,
time = Time(current=current_time,
is_daytime=is_daytime,
moon_phase=moon_phase,
sundial_cooldown=sundial_cooldown,
@ -308,9 +350,9 @@ class World:
defeated_ice_queen = f.bool()
defeated_santa_nk1 = f.bool()
defeated_everscream = f.bool()
defeated_pillars = h.PillarsInfo(solar=f.bool(), vortex=f.bool(), nebula=f.bool(), stardust=f.bool())
defeated_pillars = PillarsInfo(solar=f.bool(), vortex=f.bool(), nebula=f.bool(), stardust=f.bool())
lunar_events = h.LunarEvents(pillars_present=h.PillarsInfo(solar=f.bool(),
lunar_events = LunarEvents(pillars_present=PillarsInfo(solar=f.bool(),
vortex=f.bool(),
nebula=f.bool(),
stardust=f.bool()),
@ -323,17 +365,17 @@ class World:
partying_npcs = []
for _ in range(partying_npcs_count):
partying_npcs.append(f.int4())
party = h.Party(thrown_by_party_center=party_center_active,
party = Party(thrown_by_party_center=party_center_active,
thrown_by_npcs=party_natural_active,
cooldown=party_cooldown,
partying_npcs=partying_npcs)
sandstorm = h.Sandstorm(is_active=f.bool(),
sandstorm = Sandstorm(is_active=f.bool(),
time_left=f.int4(),
severity=f.single(),
intended_severity=f.single())
events = h.Events(blood_moon=blood_moon,
events = Events(blood_moon=blood_moon,
solar_eclipse=eclipse,
invasion=invasion,
slime_rain=time_left_slime_rain,
@ -343,7 +385,7 @@ class World:
lunar_events=lunar_events)
saved_bartender = f.bool()
saved_npcs = h.SavedNPCs(goblin_tinkerer=saved_goblin_tinkerer,
saved_npcs = SavedNPCs(goblin_tinkerer=saved_goblin_tinkerer,
wizard=saved_wizard,
mechanic=saved_mechanic,
angler=saved_angler,
@ -351,9 +393,9 @@ class World:
tax_collector=saved_tax_collector,
bartender=saved_bartender)
old_ones_army = h.OldOnesArmyTiers(f.bool(), f.bool(), f.bool())
old_ones_army = OldOnesArmyTiers(f.bool(), f.bool(), f.bool())
bosses_defeated = h.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,
skeletron=defeated_skeletron,
queen_bee=defeated_queen_bee,
@ -378,6 +420,8 @@ class World:
lunar_pillars=defeated_pillars,
old_ones_army=old_ones_army)
# Tile data starts here
first_tile = cls._read_tiles(f)
world = World(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,

3
test.py Normal file
View file

@ -0,0 +1,3 @@
import lihzahrd
lihzahrd.World.create_from_file(open("Small_Example.wld", "rb"))