def load_charname(self, book=None) -> None:
        """
        Read our character name; this is a bit dependant on the book number,
        which is why we pass it in here.  If the book number is not passed
        in, we will scan for map files and then use the first map's book
        number.  If there are no maps in the slot, then we'll raise a
        LoadException
        """

        # First figure out which book number we are, if we don't have it yet
        if not book:
            if not self.maps_loaded:
                self.load_maps()
            if len(self.maps) > 0:
                book = self.maps[0].book
            else:
                raise LoadException(
                    'Could not auto-detect which book version to use for charname')

        # Now do the actual loading
        if not os.path.exists(self.char_loc):
            raise LoadException(f'"char" file not found in {self.char_loc}')
        df = Savefile(self.char_loc)
        df.open_r()
        if book == 1:
            df.readint()
        else:
            df.readuchar()
        self.charname = df.readstr().decode('UTF-8')
        self.char_loaded = True
        df.close()
Exemple #2
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr()
                date = df.readstr()
                time = df.readstr()
                map_or_version = df.readstr()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if map_or_version.startswith('book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2
Exemple #3
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a character file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            # The initial "zero" padding in Book 1 is four bytes, and only one byte in
            # Book 2.  Since the next bit of data is the character name, as a string,
            # if the second byte of the file is 00, we'll assume that it's a Book 1 file,
            # and Book 2 otherwise.
            try:
                df.open_r()
                initital = df.readuchar()
                second = df.readuchar()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if second == 0:
                book = 1
            else:
                book = 2
Exemple #4
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr()
                date = df.readstr()
                time = df.readstr()
                map_or_version = df.readstr()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if map_or_version.startswith('book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2
Exemple #5
0
    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (range(127) + range(132, 142) + range(143, 153) +
                range(154, 161) + range(214, 251)):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (range(251, 256) + range(127, 132) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)
    def load(filename, book=None, req_book=None):
        """
        Static method to load a character file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            # The initial "zero" padding in Book 1 is four bytes, and only one byte in
            # Book 2.  Since the next bit of data is the character name, as a string,
            # if the second byte of the file is 00, we'll assume that it's a Book 1 file,
            # and Book 2 otherwise.
            try:
                df.open_r()
                initital = df.readuchar()
                second = df.readuchar()
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if second == 0:
                book = 1
            else:
                book = 2
Exemple #7
0
    def load(filename, book=None, req_book=None):
        """
        Static method to load a map file.  This will open the file once
        and read in a bit of data to determine whether this is a Book 1 map file
        or a Book 1 map file, and then call the appropriate constructor and
        return the object.
        """
        df = Savefile(filename)

        # Book 1 files start with 10 strings, Book 2 with 9, and Book 3 with
        # more.  To see what kind of file we have, read 11 strings and check
        # whether the last two are ASCII-only
        if book is None:
            try:
                df.open_r()
                strings = []
                for i in range(11):
                    strings.append(df.readstr())
                df.close()
            except (IOError, struct.error), e:
                raise LoadException(str(e))

            if not Map.is_ascii(strings[9]):
                book = 2
            elif not Map.is_ascii(strings[10]):
                book = 1
            else:
                book = 3
Exemple #8
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.

        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1

        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error) as e:
            raise LoadException(str(e))

        if nextbyte == 1:
            detected_book = 2
            detected_mapname = stringlist[0]
        # TODO: We're checking for a blank string here to cover up
        # for some invalid data that older versions of the unofficial
        # pre-1.0.0 builds.  By the time 1.1.0 rolls around, or so,
        # we should get rid of that.
        elif stringlist[0] == '0.992' or stringlist[0] == '':
            detected_book = 3
            detected_mapname = stringlist[1]
        else:
            detected_book = 1
            detected_mapname = stringlist[1]

        return detected_book, detected_mapname, df
Exemple #9
0
    def replicate(self):
        newsn = Savename.load(self.df.filename, self.book)

        if self.book == 1:
            newsn = B1Savename(Savefile(self.df.filename))
        elif self.book == 2:
            newsn = B2Savename(Savefile(self.df.filename))
        elif self.book == 3:
            newsn = B3Savename(Savefile(self.df.filename))

        # Single vals (no need to do actual replication)
        newsn.savename = self.savename
        newsn.savedate = self.savedate
        newsn.savetime = self.savetime
        newsn.mapname = self.mapname
        newsn.totalsecs = self.totalsecs
        newsn.totalturns = self.totalturns
        newsn.totaldays = self.totaldays
        newsn.coloration = self.coloration
        newsn.quicktravel = self.quicktravel

        # Lists that need copying
        for val in self.options:
            newsn.options.append(val)
        for val in self.narratives:
            newsn.narratives.append(val)
        for val in self.quests:
            newsn.quests.append(val)
        for val in self.npcs:
            newsn.npcs.append(val)

        # Call out to the subclass replication function
        self._sub_replicate(newsn)

        # Now return our duplicated object
        return newsn
Exemple #10
0
    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (list(range(127)) + list(range(132, 142)) + list(range(143, 153)) +
                  list(range(154, 161)) + list(range(214, 251))):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (list(range(251, 256)) + list(range(127, 132)) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)
    def load(filename, book=None, req_book=None):
        """
        Static method to load a savename file.  This will open the file once and
        read in a bit of data to determine whether this is a Book 1 character file or
        a Book 2 character file, and then call the appropriate constructor and
        return the object.  The individual Book constructors expect to be passed in
        an 
        """
        df = Savefile(filename)

        # First figure out what format to load, if needed
        if book is None:
            try:
                df.open_r()
                name = df.readstr().decode('UTF-8')
                date = df.readstr().decode('UTF-8')
                time = df.readstr().decode('UTF-8')
                map_or_version = df.readstr().decode('UTF-8')
                df.close()
            except (IOError, struct.error) as e:
                LOG.error("Failed to load book", exc_info=True)
                raise LoadException(e) from e

            if map_or_version.startswith(b'book3'):
                book = 3
            elif map_or_version in B1Constants.maps:
                book = 1
            else:
                book = 2

        # See if we're required to conform to a specific book
        if (req_book is not None and book != req_book):
            raise LoadException(
                'This utility can only load Book %d Character files; this file is from Book %d' % (req_book, book))

        # Now actually return the object
        if book == 1:
            c.switch_to_book(1)
            return B1Savename(df)
        elif book == 2:
            c.switch_to_book(2)
            return B2Savename(df)
        else:
            c.switch_to_book(3)
            return B3Savename(df)
Exemple #12
0
 def new(filename, book, map_df=None, ent_df=None):
     """
     Sets up a new, blank Map object with the given book.  Will raise a
     LoadException if we're passed a book we don't know about.  Optionally
     pass in a datafile object to load our data from.
     """
     if map_df is None:
         df = Savefile(filename)
     else:
         df = map_df
     if book == 1:
         c.switch_to_book(1)
         return B1Map(df, ent_df)
     elif book == 2:
         c.switch_to_book(2)
         return B2Map(df, ent_df)
     elif book == 3:
         c.switch_to_book(3)
         return B3Map(df, ent_df)
     else:
         raise LoadException('Unknown book version specified: %d' % (book))
Exemple #13
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.
        
        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1
        
        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error), e:
            raise LoadException(str(e))
Exemple #14
0
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.
        
        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1
        
        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error), e:
            raise LoadException(str(e))
Exemple #15
0
class B1Gfx(Gfx):
    """
    Grphics structure for Book 1
    """

    book = 1
    wall_types: Dict[Any, Any] = {}
    wall_gfx_group: Dict[Any, Any] = {}
    tilebuf_mult = 1
    item_dim = 42
    item_cols = 10
    item_rows = 24
    tile_width = 52
    tile_height = 26
    floor_cols = 6
    floor_rows = 32
    decal_cols = 6
    decal_rows = 32
    obj_a_width = 52
    obj_a_height = 52
    obj_a_cols = 6
    obj_a_rows = 16
    obj_a_offset = 1
    obj_b_width = 52
    obj_b_height = 78
    obj_b_cols = 6
    obj_b_rows = 10
    obj_b_offset = 101
    obj_c_width = 52
    obj_c_height = 78
    obj_c_cols = 6
    obj_c_rows = 10
    obj_c_offset = 161
    obj_d_width = 52
    obj_d_height = 130
    obj_d_cols = 5
    obj_d_rows = 1
    obj_d_offset = 251
    walldecal_cols = 6
    walldecal_rows = 10

    GFX_SET_A = 1
    GFX_SET_B = 2
    GFX_SET_C = 3
    GFX_SET_TREE = 4

    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (list(range(127)) + list(range(132, 142)) + list(range(143, 153)) +
                  list(range(154, 161)) + list(range(214, 251))):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (list(range(251, 256)) + list(range(127, 132)) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)

    def readfile(self, filename: str) -> object:
        """ Reads a given filename out of the PAK. """
        if self.loaded:
            filepath = os.path.join(
                self.eschalondata.gamedir, 'packedgraphics', filename)
            if os.path.isfile(filepath):
                return open(filepath, 'rb').read()
            if filename in self.fileindex:
                self.df.open_r()
                self.df.seek(self.zeroindex +
                             self.fileindex[filename].abs_index)
                # On Windows, we need to specify bufsize or memory gets clobbered
                filedata = zlib.decompress(
                    self.df.read(self.fileindex[filename].size_compressed),
                    15,
                    self.fileindex[filename].size_real)
                self.df.close()
                return filedata
            else:
                raise LoadException(
                    'Filename %s not found in archive' % (filename))
        else:
            raise LoadException('PAK Index has not been loaded')

    def initialread(self):
        """ Read in the main file index. """

        df = self.df
        if df is None:
            self.loaded = True
            return

        df.open_r()
        header = df.read(4)
        if (header != b'!PAK'):
            df.close()
            raise LoadException('Invalid PAK header')

        # Initial Values
        self.unknownh1 = df.readshort()
        self.unknownh2 = df.readshort()
        self.numfiles = df.readint()
        self.compressed_idx_size = df.readint()
        self.unknowni1 = df.readint()

        # Now load in the index
        decobj = zlib.decompressobj()
        indexdata = decobj.decompress(df.read())
        self.zeroindex = df.tell() - len(decobj.unused_data)
        for i in range(self.numfiles):
            index = PakIndex(indexdata[:272])
            indexdata = indexdata[272:]
            self.fileindex[index.filename] = index

        # Close and clean up
        df.close()
        self.loaded = True

    def get_item(self, item, size=None, gdk=True):
        if (self.itemcache is None):
            self.itemcache = GfxCache(self.readfile(
                'items_mastersheet.png'), 42, 42, 10)
        return self.itemcache.getimg(item.pictureid + 1, size, gdk)

    def get_floor(self, floornum, size=None, gdk=False):
        if (floornum == 0):
            return None
        if (self.floorcache is None):
            self.floorcache = GfxCache(self.readfile(
                'iso_tileset_base.png'), 52, 26, 6)
        return self.floorcache.getimg(floornum, size, gdk)

    def get_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.decalcache is None):
            self.decalcache = GfxCache(self.readfile(
                'iso_tileset_base_decals.png'), 52, 26, 6)
        return self.decalcache.getimg(decalnum, size, gdk)

    # Returns a tuple, first item is the surface, second is the extra height to add while drawing
    def get_object(self, objnum, size=None, gdk=False, treeset=0):
        """
        Note that we ignore the treeset flag in book 1
        """
        if (objnum == 0):
            return (None, 0, 0)
        try:
            gfxgroup = self.wall_gfx_group[objnum]
        except KeyError:
            return (None, 0, 0)
        if gfxgroup == self.GFX_SET_A:
            if (self.objcache1 is None):
                self.objcache1 = GfxCache(self.readfile(
                    'iso_tileset_obj_a.png'), 52, 52, 6)
            return (self.objcache1.getimg(objnum, size, gdk), 1, 0)
        elif gfxgroup == self.GFX_SET_B:
            if (self.objcache2 is None):
                self.objcache2 = GfxCache(self.readfile(
                    'iso_tileset_obj_b.png'), 52, 78, 6)
            return (self.objcache2.getimg(objnum - 100, size, gdk), 2, 0)
        elif gfxgroup == self.GFX_SET_C:
            if (self.objcache3 is None):
                self.objcache3 = GfxCache(self.readfile(
                    'iso_tileset_obj_c.png'), 52, 78, 6)
            return (self.objcache3.getimg(objnum - 160, size, gdk), 2, 0)
        else:
            if (self.objcache4 is None):
                self.objcache4 = GfxCache(
                    self.readfile('iso_trees.png'), 52, 130, 5)
            if (objnum in self.treemap):
                return (self.objcache4.getimg(self.treemap[objnum], size, gdk), 4, 0)
            else:
                return (None, 4, 0)

    def get_object_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.objdecalcache is None):
            self.objdecalcache = GfxCache(self.readfile(
                'iso_tileset_obj_decals.png'), 52, 78, 6)
        return self.objdecalcache.getimg(decalnum, size, gdk)

    def get_flame(self, size=None, gdk=False):
        """
        Grabs the flame graphic, so it's clear when viewing maps.  I provide my own
        image here instead of using the game's because the file bundled with the game
        doesn't have transparency information, and I don't feel like doing a conversion.
        """
        if (self.flamecache is None):
            with open(os.path.join(self.datadir, 'torch_single.png'), 'rb') as df:
                flamedata = df.read()
            self.flamecache = B1GfxEntCache(flamedata, 1, 1)
        if (size is None):
            size = self.tile_width
        return self.flamecache.getimg(1, int(size * self.flamecache.size_scale), gdk)

    def get_entity(self, entnum, direction, size=None, gdk=False):
        entity = self.eschalondata.get_entity(entnum)
        if not entity:
            return None
        entnum = entity.gfxfile
        if (entnum not in self.entcache):
            filename = 'mo%d.png' % (entnum)
            if (entnum in self.restrict_ents):
                self.entcache[entnum] = B1GfxEntCache(
                    self.readfile(filename), 2, 1)
            else:
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename))
        cache = self.entcache[entnum]
        if (size is None):
            size = self.tile_width
        return cache.getimg(direction, int(size * cache.size_scale), gdk)

    def get_avatar(self, avatarnum):
        if avatarnum < 0 or avatarnum > 7:
            return None
        if avatarnum not in self.avatarcache:
            if avatarnum == 7:
                if os.path.exists(os.path.join(self.eschalondata.gamedir, 'mypic.png')):
                    self.avatarcache[avatarnum] = GdkPixbuf.Pixbuf.new_from_file(
                        os.path.join(self.eschalondata.gamedir, 'mypic.png'))
                else:
                    return None
            else:
                self.avatarcache[avatarnum] = GfxCache(
                    self.readfile('{}.png'.format(avatarnum)), 60, 60, 1).pixbuf
        return self.avatarcache[avatarnum]
Exemple #16
0
 def set_df_ent(self):
     try:
         self.df_ent = Savefile(
             self.df.filename[:self.df.filename.rindex('.map')] + '.ent')
     except ValueError:
         self.df_ent = Savefile('')
Exemple #17
0
    def replicate(self):

        if self.df_ent is None:
            new_df_ent = None
        else:
            new_df_ent = Savefile(self.df_ent.filename, self.df_ent.stringdata)

        if self.book == 1:
            newmap = B1Map(Savefile(self.df.filename, self.df.stringdata),
                           new_df_ent)
        elif self.book == 2:
            newmap = B2Map(Savefile(self.df.filename, self.df.stringdata),
                           new_df_ent)
        elif self.book == 3:
            newmap = B3Map(Savefile(self.df.filename, self.df.stringdata),
                           new_df_ent)

        # Single vals (no need to do actual replication)
        newmap.mapname = self.mapname
        newmap.music1 = self.music1
        newmap.music2 = self.music2
        newmap.skybox = self.skybox
        newmap.atmos_sound_day = self.atmos_sound_day
        newmap.color_r = self.color_r
        newmap.color_g = self.color_g
        newmap.color_b = self.color_b
        newmap.color_a = self.color_a
        newmap.extradata = self.extradata
        newmap.tree_set = self.tree_set
        newmap.parallax_x = self.parallax_x
        newmap.parallax_y = self.parallax_y

        # Copy tiles
        for i in range(200):
            for j in range(100):
                newmap.tiles[i][j] = self.tiles[i][j].replicate()

        # At this point, tilecontents and entities have been replicated as well;
        # loop through our list to repopulate from the new objects, so that
        # our referential comparisons still work on the new copy.
        for entity in self.entities:
            if (entity is None):
                newmap.entities.append(None)
            else:
                if (entity.y < len(newmap.tiles)
                        and entity.x < len(newmap.tiles[entity.y])):
                    newmap.entities.append(
                        newmap.tiles[entity.y][entity.x].entity)
                else:
                    newmap.entities.append(entity.replicate())
        tilecontentidxtemp = {}
        for tilecontent in self.tilecontents:
            if (tilecontent is None):
                newmap.tilecontents.append(None)
            else:
                if (tilecontent.y < len(newmap.tiles)
                        and tilecontent.x < len(newmap.tiles[tilecontent.y])):
                    key = '%d%02d' % (tilecontent.y, tilecontent.x)
                    if (key in tilecontentidxtemp):
                        tilecontentidxtemp[key] += 1
                    else:
                        tilecontentidxtemp[key] = 0
                    newmap.tilecontents.append(newmap.tiles[tilecontent.y][
                        tilecontent.x].tilecontents[tilecontentidxtemp[key]])
                else:
                    newmap.tilecontents.append(tilecontent.replicate())

        # Call out to superclass replication
        self._sub_replicate(newmap)

        # Now return our duplicated object
        return newmap
Exemple #18
0
 def set_df_ent(self):
     try:
         self.df_ent = Savefile(
             self.df.filename[:self.df.filename.rindex('.map')] + '.ent')
     except ValueError:
         self.df_ent = Savefile('')
Exemple #19
0
class Map(object):
    """ The base Map class.  """

    DIR_NO_CHANGE = 0x00
    DIR_N = 0x01
    DIR_NE = 0x02
    DIR_E = 0x04
    DIR_SE = 0x08
    DIR_S = 0x10
    DIR_SW = 0x20
    DIR_W = 0x40
    DIR_NW = 0x80
    DIR_NOT_ADJACENT = 0xFF

    DELTA_TO_DIRECTIONS = {
        (0, 0): DIR_NO_CHANGE,
        (0, -2): DIR_N, (1, 0): DIR_E,
        (0, 2): DIR_S, (-1, 0): DIR_W}
    DELTA_TO_DIRECTIONS_EVEN = {
        (-1, -1): DIR_NW, (0, -1): DIR_NE,
        (0, 1): DIR_SE, (-1, 1): DIR_SW}
    DELTA_TO_DIRECTIONS_ODD = {
        (0, -1): DIR_NW, (1, -1): DIR_NE,
        (1, 1): DIR_SE, (0, 1): DIR_SW}

    DIRECTIONS_TO_DELTA = {
        DIR_NO_CHANGE: (0, 0),
        DIR_N: (0, -2), DIR_E: (1, 0),
        DIR_S: (0, 2), DIR_W: (-1, 0)}
    DIRECTIONS_TO_DELTA_EVEN = {
        DIR_NW: (-1, -1), DIR_NE: (0, -1),
        DIR_SE: (0, 1), DIR_SW: (-1, 1)}
    DIRECTIONS_TO_DELTA_ODD = {
        DIR_NW: (0, -1), DIR_NE: (1, -1),
        DIR_SE: (1, 1), DIR_SW: (0, 1)}

    def __init__(self, df, ent_df):
        """
        A fresh object.
        """

        # Everything else follows...
        self.df = None
        self.df_ent = None
        self.filename_ent = ''
        self.mapname = ''
        self.music1 = ''
        self.music2 = ''
        self.skybox = ''
        self.atmos_sound_day = ''

        # Not entirely sure about the alpha channel, which
        # is always zero, but it seems to make sense
        self.color_r = 255
        self.color_g = 255
        self.color_b = 255
        self.color_a = 0

        self.parallax_x = 0
        self.parallax_y = 0

        self.extradata = ''

        # Note that book 1 doesn't actually have this, but for sanity's
        # sake we're putting it in the base class
        self.tree_set = 0

        self.cursqcol = 0
        self.cursqrow = 0

        self.tiles = []
        for i in range(200):
            self.tiles.append([])
            for j in range(100):
                self.tiles[i].append(Tile.new(c.book, j, i))

        self.tilecontents = []
        self.entities = []

        self.df = df
        if ent_df is None:
            self.set_df_ent()
        else:
            self.df_ent = ent_df

        # Also, we'll keep track of "big graphic" mappings
        self.big_gfx_mappings = BigGraphicMappings(self)

    def set_savegame(self, savegame):
        """
        Sets the savegame flags as-requested.
        """
        for row in self.tiles:
            for tile in row:
                tile.savegame = savegame
        for entity in self.entities:
            entity.savegame = savegame
        for tilecontent in self.tilecontents:
            tilecontent.savegame = savegame

    def check_map_extension(self):
        """
        Force the map to have a .map extension.  Note that our "Save As" logic
        might not warn on overwriting, now, because of this.
        """
        if self.df.filename[-4:].lower() != '.map':
            self.df.filename = '%s.map' % self.df.filename

    def set_df_ent(self):
        try:
            self.df_ent = Savefile(
                self.df.filename[:self.df.filename.rindex('.map')] + '.ent')
        except ValueError:
            self.df_ent = Savefile('')

    def get_opq_path(self):
        """
        Returns the path to the opq file (only valid if we're a savegame).  This is
        the level minimap, and must be present in the savegame dir.
        """
        return self.df.filename[:self.df.filename.rindex('.map')] + '.opq'

    def has_opq_file(self):
        """
        Returns true if we have a .opq file in our savegame dir, or if we're a
        global map file.  Returns false if we're a savegame and an .opq is not
        found.
        """
        if self.is_savegame() and not os.path.exists(self.get_opq_path()):
            return False
        else:
            return True

    def replicate(self):

        if self.df_ent is None:
            new_df_ent = None
        else:
            new_df_ent = Savefile(self.df_ent.filename, self.df_ent.stringdata)

        if self.book == 1:
            newmap = B1Map(Savefile(self.df.filename,
                                    self.df.stringdata), new_df_ent)
        elif self.book == 2:
            newmap = B2Map(Savefile(self.df.filename,
                                    self.df.stringdata), new_df_ent)
        elif self.book == 3:
            newmap = B3Map(Savefile(self.df.filename,
                                    self.df.stringdata), new_df_ent)

        # Single vals (no need to do actual replication)
        newmap.mapname = self.mapname
        newmap.music1 = self.music1
        newmap.music2 = self.music2
        newmap.skybox = self.skybox
        newmap.atmos_sound_day = self.atmos_sound_day
        newmap.color_r = self.color_r
        newmap.color_g = self.color_g
        newmap.color_b = self.color_b
        newmap.color_a = self.color_a
        newmap.extradata = self.extradata
        newmap.tree_set = self.tree_set
        newmap.parallax_x = self.parallax_x
        newmap.parallax_y = self.parallax_y

        # Copy tiles
        for i in range(200):
            for j in range(100):
                newmap.tiles[i][j] = self.tiles[i][j].replicate()

        # At this point, tilecontents and entities have been replicated as well;
        # loop through our list to repopulate from the new objects, so that
        # our referential comparisons still work on the new copy.
        for entity in self.entities:
            if entity is None:
                newmap.entities.append(None)
            else:
                if entity.y < len(newmap.tiles) and entity.x < len(newmap.tiles[entity.y]):
                    newmap.entities.append(
                        newmap.tiles[entity.y][entity.x].entity)
                else:
                    newmap.entities.append(entity.replicate())
        tilecontentidxtemp = {}
        for tilecontent in self.tilecontents:
            if tilecontent is None:
                newmap.tilecontents.append(None)
            else:
                if tilecontent.y < len(newmap.tiles) and tilecontent.x < len(newmap.tiles[tilecontent.y]):
                    key = '%d%02d' % (tilecontent.y, tilecontent.x)
                    if key in tilecontentidxtemp:
                        tilecontentidxtemp[key] += 1
                    else:
                        tilecontentidxtemp[key] = 0
                    newmap.tilecontents.append(
                        newmap.tiles[tilecontent.y][tilecontent.x].tilecontents[tilecontentidxtemp[key]])
                else:
                    newmap.tilecontents.append(tilecontent.replicate())

        # Call out to superclass replication
        self._sub_replicate(newmap)

        # Now return our duplicated object
        return newmap

    def _sub_replicate(self, newmap):
        """
        Stub for superclasses to override, to replicate specific vars
        """
        pass

    def set_tile_savegame(self):
        """ Sets the savegame flag appropriately for all tiles """
        savegame = self.is_savegame()
        for row in self.tiles:
            for tile in row:
                tile.savegame = savegame

    def addtile(self):
        """ Add a new tile, assuming that the tiles are stored in a
            left-to-right, top-to-bottom format in the map. """
        self.tiles[self.cursqrow][self.cursqcol].read(self.df)
        self.cursqcol += 1
        if self.cursqcol == 100:
            self.cursqcol = 0
            self.cursqrow += 1

    def addtilecontent(self):
        """ Add a tilecontent. """
        try:
            tilecontent = Tilecontent.new(c.book, self.is_savegame())
            tilecontent.read(self.df)
            # Note that once we start deleting tilecontents, you'll have to update both constructs here.
            # Something along the lines of this should do:
            #   self.map.tiles[y][x].tilecontents.remove(tilecontent)
            #   self.tilecontents.remove(tilecontent)
            # ... does that object then get put into a garbage collector or something?  Do we have to
            # set that to None at some point, manually?
            self.tilecontents.append(tilecontent)
            if 0 <= tilecontent.x < 100 and 0 <= tilecontent.y < 200:
                self.tiles[tilecontent.y][tilecontent.x].addtilecontent(
                    tilecontent)
            return True
        except FirstItemLoadException as e:
            return False

    def deltilecontent(self, x, y, idx):
        """ Deletes a tilecontent, both from the associated tile, and our internal list. """
        tile = self.tiles[y][x]
        tilecontent = tile.tilecontents[idx]
        if tilecontent is not None:
            self.tilecontents.remove(tilecontent)
            self.tiles[y][x].deltilecontent(tilecontent)

    def addentity(self):
        """ Add an entity. """
        try:
            entity = Entity.new(c.book, self.is_savegame())
            entity.read(self.df_ent)
            if self.tiles[entity.y][entity.x].entity is not None:
                # TODO: Support this better, perhaps?
                LOG.warn(
                    'Two entities on a single tile, discarding all but the original')
            else:
                self.entities.append(entity)
                if 0 <= entity.x < 100 and 0 <= entity.y < 200:
                    self.tiles[entity.y][entity.x].addentity(entity)
            return True
        except FirstItemLoadException as e:
            return False

    def delentity(self, x, y):
        """ Deletes an entity, both from the associated tile, and our internal list. """
        tile = self.tiles[y][x]
        ent = tile.entity
        if ent is not None:
            self.entities.remove(ent)
            tile.delentity()

    def rgb_color(self):
        return (self.color_r << 24) + (self.color_g << 16) + (self.color_b << 8) + 0xFF

    def coords_relative(self, x, y, dir):
        """
        Static method to return coordinates for the tile
        relative to the given coords.  1 = N, 2 = NE, etc
        """
        if dir == self.DIR_N:
            if y < 2:
                return None
            else:
                return x, y - 2
        elif dir == self.DIR_NE:
            if (y % 2) == 0:
                if y > 0:
                    return x, y - 1
                else:
                    return None
            elif x < 99:
                return x + 1, y - 1
            else:
                return None
        elif dir == self.DIR_E:
            if x < 99:
                return x + 1, y
            else:
                return None
        elif dir == self.DIR_SE:
            if (y % 2) == 0:
                return x, y + 1
            elif x < 99 and y < 199:
                return x + 1, y + 1
            else:
                return None
        elif dir == self.DIR_S:
            if y < 198:
                return x, y + 2
            else:
                return None
        elif dir == self.DIR_SW:
            if (y % 2) == 1:
                if y < 199:
                    return x, y + 1
                else:
                    return None
            elif x > 0:
                return x - 1, y + 1
            else:
                return None
        elif dir == self.DIR_W:
            if x > 0:
                return x - 1, y
            else:
                return None
        elif dir == self.DIR_NW:
            if (y % 2) == 1:
                return x, y - 1
            elif y > 0 and x > 0:
                return x - 1, y - 1
            else:
                return None
        else:
            return None

    def tile_relative(self, x, y, dir):
        """ Returns a tile object relative to the given coords. """
        coords = self.coords_relative(x, y, dir)
        if coords:
            return self.tiles[coords[1]][coords[0]]
        else:
            return None

    def _convert_savegame(self, savegame):
        """
        Does the grunt work of converting ourself to a savegame or global
        file.
        """
        for col in self.tiles:
            for tile in col:
                tile._convert_savegame(savegame)
        for entity in self.entities:
            entity._convert_savegame(savegame)
        self.set_savegame(savegame)

    def convert_savegame(self, savegame=True):
        """
        Converts ourself to a savegame or global map.  This will clear out the
        filenames from our Savefile objects, so they can't be accidentally
        overwritten without effort.
        """
        if (savegame and self.is_global()) or (self.is_savegame() and not savegame):
            self.df.set_filename('')
            if self.df_ent is not None:
                self.df_ent.set_filename('')
            self._convert_savegame(savegame)
            return True
        else:
            raise Exception('No conversion to perform')

    def get_item_names(self):
        """
        Returns a list of tuples which describe all the item names
        found on the map.  Used at the moment to doublecheck item names
        once a map is converted from savegame to global.  Elements of
        the tuple:
            1) Tile X
            2) Tile Y
            3) Item Name
        """
        retlist = []
        for (y, row) in enumerate(self.tiles):
            for (x, tile) in enumerate(row):
                for tilecontent in tile.tilecontents:
                    for item in tilecontent.items:
                        if item.item_name != '':
                            retlist.append((x, y, item.item_name))
        return retlist

    def get_invalid_global_items(self):
        """
        Returns a list of items on this map which do not appear to
        be valid global item names.  Returns a list of tuples
        where each element contains the following:
            1) Tile X
            2) Tile Y
            3) Item Name
        """
        itemdict = c.eschalondata.get_itemdict()
        invalid_items = []
        for itemtuple in self.get_item_names():
            itemname = itemtuple[2]
            itemname_lower = itemname.lower()
            if (itemname_lower != 'empty' and itemname_lower != 'random' and
                    itemname not in itemdict):
                invalid_items.append(itemtuple)
        return invalid_items

    @staticmethod
    def new(filename, book, map_df=None, ent_df=None):
        """
        Sets up a new, blank Map object with the given book.  Will raise a
        LoadException if we're passed a book we don't know about.  Optionally
        pass in a datafile object to load our data from.
        """
        if map_df is None:
            df = Savefile(filename)
        else:
            df = map_df
        if book == 1:
            c.switch_to_book(1)
            return B1Map(df, ent_df)
        elif book == 2:
            c.switch_to_book(2)
            return B2Map(df, ent_df)
        elif book == 3:
            c.switch_to_book(3)
            return B3Map(df, ent_df)
        else:
            raise LoadException('Unknown book version specified: %d' % book)

    @staticmethod
    def get_mapinfo(filename=None, map_df=None):
        """
        Given a filename or a passed filehandle, loads the first few bits of
        information from a map file, and will return a tuple containing the
        Eschalon Book the map belongs to, the internal "map name" of the map,
        and a Savefile object pointing to the map.  Will raise a LoadException
        if it encounters errors.

        Book 1 files start with 10 strings
        Book 2 files start with 9 strings, followed by a uchar whose value
          will always be 1 (the "loadhook" var, presumably)
        Book 3 files start with 12 strings, the first of which is a version,
          which so far is always 0.992.

        So, to figure out dynamically what kind of file we're loading:
          1) Read 9 strings, remember the first one
          2) Read the next uchar - if it's 1, then we're editing Book 2
          3) If the first string is "0.992", then we're editing Book 3
          4) Otherwise, we're editing Book 1

        Theoretically, that way this works even if a Book 2 map happens
        to use a mapname of 0.992, in an effort to be cheeky.
        """
        if filename is not None:
            df = Savefile(filename)
        elif map_df is not None:
            df = map_df
        else:
            raise LoadException('One of filename or map_df must be passed in')
        stringlist = []
        try:
            df.open_r()
            for i in range(9):
                stringlist.append(df.readstr())
            nextbyte = df.readuchar()
            df.close()
        except (IOError, struct.error) as e:
            raise LoadException(str(e))

        if nextbyte == 1:
            detected_book = 2
            detected_mapname = stringlist[0]
        # TODO: We're checking for a blank string here to cover up
        # for some invalid data that older versions of the unofficial
        # pre-1.0.0 builds.  By the time 1.1.0 rolls around, or so,
        # we should get rid of that.
        elif stringlist[0] == '0.992' or stringlist[0] == '':
            detected_book = 3
            detected_mapname = stringlist[1]
        else:
            detected_book = 1
            detected_mapname = stringlist[1]

        return detected_book, detected_mapname, df

    @staticmethod
    def load(filename, req_book=None):
        """
        Static method to load a map file.  This will open the file once
        and read in a bit of data to determine which Eschalon game the mapfile
        comes from, and calls the appropriate constructor to return the object.
        If req_book is passed in, it will raise a LoadException if the detected
        Book number doesn't match.  This will also raise a LoadException if
        it's unable to determine the version (generally due to being passed
        something that's not a map file).

        Note that this method does not actually read in the entire map file.
        It does "preload" the map name, however, so that it can be easily
        referenced in lists.  Use .read() on the resulting map object to actually
        read in the map data.
        """

        # Get some information about the filename
        (detected_book, detected_mapname, df) = Map.get_mapinfo(filename)

        # See if we're required to conform to a specific book
        if req_book is not None and detected_book != req_book:
            raise LoadException('This utility can only load Book %d map files; this file is from Book %d' % (
                req_book, detected_book))

        # Now actually return the object
        if detected_book == 1:
            c.switch_to_book(1)
            return B1Map(df)
        elif detected_book == 2:
            c.switch_to_book(2)
            return B2Map(df)
        elif detected_book == 3:
            c.switch_to_book(3)
            return B3Map(df)
        else:
            raise LoadException(
                'Unknown book version found for "%s"; perhaps it is not an Eschalon map file' % filename)

    # Find directions from one coordinate set to another
    @staticmethod
    def directions_between_coords(x1, y1, x2, y2):
        if y1 % 2 == 0:
            map = dict(list(Map.DELTA_TO_DIRECTIONS.items()) +
                       list(Map.DELTA_TO_DIRECTIONS_EVEN.items()))
        else:
            map = dict(list(Map.DELTA_TO_DIRECTIONS.items()) +
                       list(Map.DELTA_TO_DIRECTIONS_ODD.items()))

        xdiff = x2 - x1
        ydiff = y2 - y1

        # Base case - adjacent tile
        if (xdiff, ydiff) in map:
            if map[(xdiff, ydiff)] == 0:
                return []
            else:
                return [map[(xdiff, ydiff)]]

        # Not adjacent - recur
        # Looping through cardinal directions first would produce
        # shorter lists of directions, but it's probably not worth the
        # extra code complexity
        for coords in map:
            # Don't allow direction 0, DIR_NO_CHANGE
            if map[coords] == 0:
                continue
            # Does this direction get us closer?
            newx = x1 + coords[0]
            newy = y1 + coords[1]
            if abs(x2 - newx) <= abs(xdiff) and abs(y2 - newy) <= abs(ydiff):
                return [map[coords]] + Map.directions_between_coords(newx, newy, x2, y2)

        # Should never happen
        raise Exception("Couldn't find a direction from " + str(x1) +
                        "," + str(y1) + " to " + str(x2) + "," + str(y2))

    # Follow a set of directions from a coordinate set
    @staticmethod
    def follow_directions_from_coord(x, y, directions):
        for direction in directions:
            if direction in Map.DIRECTIONS_TO_DELTA:
                x += Map.DIRECTIONS_TO_DELTA[direction][0]
                y += Map.DIRECTIONS_TO_DELTA[direction][1]
            elif y % 2 == 0 and direction in Map.DIRECTIONS_TO_DELTA_EVEN:
                x += Map.DIRECTIONS_TO_DELTA_EVEN[direction][0]
                y += Map.DIRECTIONS_TO_DELTA_EVEN[direction][1]
            elif y % 2 == 1 and direction in Map.DIRECTIONS_TO_DELTA_ODD:
                x += Map.DIRECTIONS_TO_DELTA_ODD[direction][0]
                y += Map.DIRECTIONS_TO_DELTA_ODD[direction][1]
            else:
                raise Exception("Unknown direction " + hex(direction))
        return x, y
Exemple #20
0
import zlib
from eschalon.savefile import Savefile, LoadException
from struct import unpack

class PakIndex(object):
    """ A class to hold information on an individual file in the pak. """

    def __init__(self, data):
        (self.size_compressed,
                self.abs_index,
                self.size_real,
                self.unknowni1) = unpack('<IIII', data[:16])
        self.filename = data[16:]
        self.filename = self.filename[:self.filename.index("\x00")]

df = Savefile(sys.argv[1])

df.open_r()
header = df.read(4)
if (header != '!PAK'):
  df.close()
  raise LoadException('Invalid PAK header')

# Initial Values
unknownh1 = df.readshort()
unknownh2 = df.readshort()
numfiles = df.readint()
compressed_idx_size = df.readint()
unknowni1 = df.readint()

# Now load in the index
Exemple #21
0
    def load_charname(self, book=None):
        """
        Read our character name; this is a bit dependant on the book number,
        which is why we pass it in here.  If the book number is not passed
        in, we will scan for map files and then use the first map's book
        number.  If there are no maps in the slot, then we'll raise a
        LoadException
        """

        # First figure out which book number we are, if we don't have it yet
        if not book:
            if not self.maps_loaded:
                self.load_maps()
            if len(self.maps) > 0:
                book = self.maps[0].book
            else:
                raise LoadException(
                    'Could not auto-detect which book version to use for charname'
                )

        # Now do the actual loading
        if not os.path.exists(self.char_loc):
            raise LoadException('"char" file not found in %s' % (directory))
        df = Savefile(self.char_loc)
        df.open_r()
        if book == 1:
            df.readint()
        else:
            df.readuchar()
        self.charname = df.readstr()
        self.char_loaded = True
        df.close()
Exemple #22
0
class B1Gfx(Gfx):
    """
    Grphics structure for Book 1
    """

    book = 1
    wall_types = {}
    wall_gfx_group = {}
    tilebuf_mult = 1
    item_dim = 42
    item_cols = 10
    item_rows = 24
    tile_width = 52
    tile_height = 26
    floor_cols = 6
    floor_rows = 32
    decal_cols = 6
    decal_rows = 32
    obj_a_width = 52
    obj_a_height = 52
    obj_a_cols = 6
    obj_a_rows = 16
    obj_a_offset = 1
    obj_b_width = 52
    obj_b_height = 78
    obj_b_cols = 6
    obj_b_rows = 10
    obj_b_offset = 101
    obj_c_width = 52
    obj_c_height = 78
    obj_c_cols = 6
    obj_c_rows = 10
    obj_c_offset = 161
    obj_d_width = 52
    obj_d_height = 130
    obj_d_cols = 5
    obj_d_rows = 1
    obj_d_offset = 251
    walldecal_cols = 6
    walldecal_rows = 10

    GFX_SET_A = 1
    GFX_SET_B = 2
    GFX_SET_C = 3
    GFX_SET_TREE = 4

    def __init__(self, datadir, eschalondata):

        # Wall graphic groupings
        for i in range(101):
            self.wall_gfx_group[i] = self.GFX_SET_A
        for i in range(101, 161):
            self.wall_gfx_group[i] = self.GFX_SET_B
        for i in range(161, 251):
            self.wall_gfx_group[i] = self.GFX_SET_C
        for i in range(251, 256):
            self.wall_gfx_group[i] = self.GFX_SET_TREE

        # Wall object types
        for i in (range(127) + range(132, 142) + range(143, 153) +
                range(154, 161) + range(214, 251)):
            self.wall_types[i] = self.TYPE_OBJ
        for i in range(161, 214):
            self.wall_types[i] = self.TYPE_WALL
        for i in (range(251, 256) + range(127, 132) + [142, 153]):
            self.wall_types[i] = self.TYPE_TREE

        # Restricted entities (only one direction)
        self.restrict_ents = [50, 55, 58, 66, 67, 71]

        # Book 1 specific vars (just the PAK structure stuff)
        self.unknownh1 = -1
        self.unknownh2 = -1
        self.numfiles = -1
        self.compressed_idx_size = -1
        self.unknowni1 = -1
        self.fileindex = {}
        self.zeroindex = -1

        # Book 1 specific caches
        self.itemcache = None

        # Graphics PAK file
        self.pakloc = os.path.join(eschalondata.gamedir, 'gfx.pak')
        if os.path.isfile(self.pakloc):
            self.df = Savefile(self.pakloc)
        else:
            self.df = None

        # Set our loaded status
        self.loaded = False

        # Finally call the parent constructor
        super(B1Gfx, self).__init__(datadir, eschalondata)

    def readfile(self, filename):
        """ Reads a given filename out of the PAK. """
        if self.loaded:
            filepath = os.path.join(self.eschalondata.gamedir, 'packedgraphics', filename)
            if os.path.isfile(filepath):
                return open(filepath, 'rb').read()
            if (filename in self.fileindex):
                self.df.open_r()
                self.df.seek(self.zeroindex + self.fileindex[filename].abs_index)
                # On Windows, we need to specify bufsize or memory gets clobbered
                filedata = zlib.decompress(
                    self.df.read(self.fileindex[filename].size_compressed),
                    15,
                    self.fileindex[filename].size_real)
                self.df.close()
                return filedata
            else:
                raise LoadException('Filename %s not found in archive' % (filename))
        else:
            raise LoadException('PAK Index has not been loaded')

    def initialread(self):
        """ Read in the main file index. """

        df = self.df
        if df is None:
            self.loaded = True
            return

        df.open_r()
        header = df.read(4)
        if (header != '!PAK'):
            df.close()
            raise LoadException('Invalid PAK header')
        
        # Initial Values
        self.unknownh1 = df.readshort()
        self.unknownh2 = df.readshort()
        self.numfiles = df.readint()
        self.compressed_idx_size = df.readint()
        self.unknowni1 = df.readint()

        # Now load in the index
        decobj = zlib.decompressobj()
        indexdata = decobj.decompress(df.read())
        self.zeroindex = df.tell() - len(decobj.unused_data)
        decobj = None
        for i in range(self.numfiles):
            index = PakIndex(indexdata[:272])
            indexdata = indexdata[272:]
            self.fileindex[index.filename] = index

        # Close and clean up
        df.close()
        self.loaded = True

    def get_item(self, item, size=None, gdk=True):
        if (self.itemcache is None):
            self.itemcache = GfxCache(self.readfile('items_mastersheet.png'), 42, 42, 10)
        return self.itemcache.getimg(item.pictureid+1, size, gdk)

    def get_floor(self, floornum, size=None, gdk=False):
        if (floornum == 0):
            return None
        if (self.floorcache is None):
            self.floorcache = GfxCache(self.readfile('iso_tileset_base.png'), 52, 26, 6)
        return self.floorcache.getimg(floornum, size, gdk)

    def get_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.decalcache is None):
            self.decalcache = GfxCache(self.readfile('iso_tileset_base_decals.png'), 52, 26, 6)
        return self.decalcache.getimg(decalnum, size, gdk)

    # Returns a tuple, first item is the surface, second is the extra height to add while drawing
    def get_object(self, objnum, size=None, gdk=False, treeset=0):
        """
        Note that we ignore the treeset flag in book 1
        """
        if (objnum == 0):
            return (None, 0, 0)
        try:
            gfxgroup = self.wall_gfx_group[objnum]
        except KeyError:
            return (None, 0, 0)
        if gfxgroup == self.GFX_SET_A:
            if (self.objcache1 is None):
                self.objcache1 = GfxCache(self.readfile('iso_tileset_obj_a.png'), 52, 52, 6)
            return (self.objcache1.getimg(objnum, size, gdk), 1, 0)
        elif gfxgroup == self.GFX_SET_B:
            if (self.objcache2 is None):
                self.objcache2 = GfxCache(self.readfile('iso_tileset_obj_b.png'), 52, 78, 6)
            return (self.objcache2.getimg(objnum-100, size, gdk), 2, 0)
        elif gfxgroup == self.GFX_SET_C:
            if (self.objcache3 is None):
                self.objcache3 = GfxCache(self.readfile('iso_tileset_obj_c.png'), 52, 78, 6)
            return (self.objcache3.getimg(objnum-160, size, gdk), 2, 0)
        else:
            if (self.objcache4 is None):
                self.objcache4 = GfxCache(self.readfile('iso_trees.png'), 52, 130, 5)
            if (objnum in self.treemap):
                return (self.objcache4.getimg(self.treemap[objnum], size, gdk), 4, 0)
            else:
                return (None, 4, 0)

    def get_object_decal(self, decalnum, size=None, gdk=False):
        if (decalnum == 0):
            return None
        if (self.objdecalcache is None):
            self.objdecalcache = GfxCache(self.readfile('iso_tileset_obj_decals.png'), 52, 78, 6)
        return self.objdecalcache.getimg(decalnum, size, gdk)

    def get_flame(self, size=None, gdk=False):
        """
        Grabs the flame graphic, so it's clear when viewing maps.  I provide my own
        image here instead of using the game's because the file bundled with the game
        doesn't have transparency information, and I don't feel like doing a conversion.
        """
        if (self.flamecache is None):
            df = open(os.path.join(self.datadir, 'torch_single.png'), 'rb')
            flamedata = df.read()
            df.close()
            self.flamecache = B1GfxEntCache(flamedata, 1, 1)
        if (size is None):
            size = self.tile_width
        return self.flamecache.getimg(1, int(size*self.flamecache.size_scale), gdk)

    def get_entity(self, entnum, direction, size=None, gdk=False):
        entity = self.eschalondata.get_entity(entnum)
        if not entity:
            return None
        entnum = entity.gfxfile
        if (entnum not in self.entcache):
            filename = 'mo%d.png' % (entnum)
            if (entnum in self.restrict_ents):
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename), 2, 1)
            else:
                self.entcache[entnum] = B1GfxEntCache(self.readfile(filename))
        cache = self.entcache[entnum]
        if (size is None):
            size = self.tile_width
        return cache.getimg(direction, int(size*cache.size_scale), gdk)

    def get_avatar(self, avatarnum):
        if (avatarnum < 0 or avatarnum > 7):
            return None
        if (avatarnum not in self.avatarcache):
            if (avatarnum == 7):
                if (os.path.exists(os.path.join(self.eschalondata.gamedir, 'mypic.png'))):
                    self.avatarcache[avatarnum] = gtk.gdk.pixbuf_new_from_file(os.path.join(self.eschalondata.gamedir, 'mypic.png'))
                else:
                    return None
            else:
                self.avatarcache[avatarnum] = GfxCache(self.readfile('%d.png' % (avatarnum)), 60, 60, 1).pixbuf
        return self.avatarcache[avatarnum]
Exemple #23
0
    def replicate(self):
        newchar = Character.load(self.df.filename, self.book)

        if self.book == 1:
            newchar = B1Character(Savefile(self.df.filename))
        elif self.book == 2:
            newchar = B2Character(Savefile(self.df.filename))
        elif self.book == 3:
            newchar = B3Character(Savefile(self.df.filename))

        # Single vals (no need to do actual replication)
        #newchar.book = self.book
        newchar.inv_rows = self.inv_rows
        newchar.inv_cols = self.inv_cols
        newchar.name = self.name
        newchar.strength = self.strength
        newchar.dexterity = self.dexterity
        newchar.endurance = self.endurance
        newchar.speed = self.speed
        newchar.intelligence = self.intelligence
        newchar.wisdom = self.wisdom
        newchar.perception = self.perception
        newchar.concentration = self.concentration
        newchar.maxhp = self.maxhp
        newchar.maxmana = self.maxmana
        newchar.curhp = self.curhp
        newchar.curmana = self.curmana
        newchar.experience = self.experience
        newchar.level = self.level
        newchar.gold = self.gold
        newchar.torches = self.torches
        newchar.torchused = self.torchused
        newchar.curinvcol = self.curinvcol
        newchar.curinvrow = self.curinvrow
        newchar.orientation = self.orientation
        newchar.xpos = self.xpos
        newchar.ypos = self.ypos
        newchar.picid = self.picid
        newchar.extra_att_points = self.extra_att_points
        newchar.extra_skill_points = self.extra_skill_points

        # Lists that need copying
        for val in self.spells:
            newchar.spells.append(val)
        for val in self.fxblock:
            newchar.fxblock.append(val)
        for val in self.statuses:
            newchar.statuses.append(val)

        # More complex lists that need copying
        for val in self.readyslots:
            newchar.readyslots.append([val[0], val[1]])

        # Dicts that need copying
        for key, val in self.skills.iteritems():
            newchar.skills[key] = val

        # Objects that need copying
        for i in range(self.inv_rows):
            for j in range(self.inv_cols):
                newchar.inventory[i][j] = self.inventory[i][j].replicate()
        for i in range(self.ready_rows * self.ready_cols):
            newchar.readyitems[i] = self.readyitems[i].replicate()
        newchar.quiver = self.quiver.replicate()
        newchar.helm = self.helm.replicate()
        newchar.cloak = self.cloak.replicate()
        newchar.amulet = self.amulet.replicate()
        newchar.torso = self.torso.replicate()
        newchar.weap_prim = self.weap_prim.replicate()
        newchar.belt = self.belt.replicate()
        newchar.gauntlet = self.gauntlet.replicate()
        newchar.legs = self.legs.replicate()
        newchar.ring1 = self.ring1.replicate()
        newchar.ring2 = self.ring2.replicate()
        newchar.shield = self.shield.replicate()
        newchar.feet = self.feet.replicate()

        # Call out to the subclass replication function
        self._sub_replicate(newchar)

        # Now return our duplicated object
        return newchar