diff --git a/nml/actions/real_sprite.py b/nml/actions/real_sprite.py --- a/nml/actions/real_sprite.py +++ b/nml/actions/real_sprite.py @@ -160,6 +160,9 @@ class RealSprite(object): @ivar flags: Cropping/warning flags. @type flags: L{expression.ConstantNumeric} + + @ivar pos: Position of creation of the sprite, if available. + @type pos: L{Position} """ def __init__(self, param_list = None, label = None): @@ -170,6 +173,7 @@ class RealSprite(object): self.ypos = None self.xsize = None self.ysize = None + self.pos = None def debug_print(self, indentation): generic.print_dbg(indentation, 'Real sprite, parameters:') @@ -249,6 +253,13 @@ class RealSprite(object): return (rgb_file, rgb_rect, mask_file, mask_rect, do_crop) class SpriteAction(base_action.BaseAction): + """ + @ivar sprite_num: Number of the sprite, or C{None} if not decided yet. + @type sprite_num: C{int} or C{None} + + @ivar last: Whether this sprite action is the last of a series. + @type last: C{bool} + """ def __init__(self): self.sprite_num = None self.last = False @@ -493,7 +504,7 @@ def parse_sprite_data(sprite_container): first = True for sprite_data in all_sprite_data: - sprite_list, default_file, default_mask_file, zoom_level, bit_depth = sprite_data + sprite_list, default_file, default_mask_file, pos, zoom_level, bit_depth = sprite_data new_sprite_list = parse_sprite_list(sprite_list, default_file, default_mask_file) if not first and len(new_sprite_list) != len(action_list): msg = "Expected {:d} alternative sprites for {} '{}', got {:d}." @@ -507,6 +518,7 @@ def parse_sprite_data(sprite_container): raise generic.ScriptError("Mask file may only be specified for 32bpp sprites.", sprite.mask_file.pos) if first: if isinstance(sprite, RealSprite): + sprite.pos = pos action_list.append(RealSpriteAction()) else: assert isinstance(sprite, RecolourSprite) diff --git a/nml/ast/sprite_container.py b/nml/ast/sprite_container.py --- a/nml/ast/sprite_container.py +++ b/nml/ast/sprite_container.py @@ -28,7 +28,7 @@ class SpriteContainer(object): @type block_name: L{Identifier}, or C{None} if N/A @ivar sprite_data: Mapping of (zoom level, bit-depth) to (sprite list, default file) - @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None})) + @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position})) """ sprite_blocks = {} @@ -50,14 +50,17 @@ class SpriteContainer(object): "level / bit depth combination. This data will be overridden.") msg = msg.format(self.block_type, self.block_name.value) generic.print_warning(msg, pos) - self.sprite_data[key] = (sprite_list, default_file, default_mask_file) + self.sprite_data[key] = (sprite_list, default_file, default_mask_file, pos) def get_all_sprite_data(self): """ - Get all sprite data as a list of 5-tuples (sprite_list, default_file, default_mask_file, zoom_level, bit_depth) - Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first + Get all sprite data. + Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first. + + @return: List of 6-tuples (sprite_list, default_file, default_mask_file, position, zoom_level, bit_depth). + @rtype: C{list} of C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position}, C{int}, C{int}) """ - return [(self.sprite_data[key][0], self.sprite_data[key][1], self.sprite_data[key][2], key[0], key[1]) for key in sorted(self.sprite_data)] + return [val + key for key, val in sorted(self.sprite_data.items())] @classmethod def resolve_sprite_block(cls, block_name): diff --git a/nml/generic.py b/nml/generic.py --- a/nml/generic.py +++ b/nml/generic.py @@ -139,8 +139,10 @@ class ImageFilePosition(Position): """ Generic (not position-dependant) error with an image file """ - def __init__(self, filename): - Position.__init__(self, filename, []) + def __init__(self, filename, pos = None): + poslist = [] + if pos is not None: poslist.append(pos) + Position.__init__(self, filename, poslist) def __str__(self): return 'Image file "{}"'.format(self.filename) @@ -181,8 +183,8 @@ class RangeError(ScriptError): ScriptError.__init__(self, name + " out of range " + str(min_value) + ".." + str(max_value) + ", encountered " + str(value), pos) class ImageError(ScriptError): - def __init__(self, value, filename): - ScriptError.__init__(self, value, ImageFilePosition(filename)) + def __init__(self, value, filename, pos = None): + ScriptError.__init__(self, value, ImageFilePosition(filename, pos)) class OnlyOnceError(ScriptError): """ diff --git a/nml/spriteencoder.py b/nml/spriteencoder.py --- a/nml/spriteencoder.py +++ b/nml/spriteencoder.py @@ -237,6 +237,9 @@ class SpriteEncoder(object): filename_32bpp = sprite_info.file filename_8bpp = sprite_info.mask_file + pos = sprite_info.pos + if pos is None: pos = sprite_info.file.pos + # Get initial info_byte and dimensions from sprite_info. # These values will be changed depending on cropping and compression. info_byte = INFO_NOCROP if (sprite_info.flags.value & real_sprite.FLAG_NOCROP) != 0 else 0 @@ -263,14 +266,14 @@ class SpriteEncoder(object): if filename_32bpp is not None: im = self.open_image_file(filename_32bpp.value) if im.mode not in ("RGB", "RGBA"): - raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value) + raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value, pos) info_byte |= INFO_RGB if im.mode == "RGBA": info_byte |= INFO_ALPHA (im_width, im_height) = im.size if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height: - raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), filename_32bpp.pos) + raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos) sprite = im.crop((x, y, x + size_x, y + size_y)) rgb_sprite_data = sprite.tostring() @@ -281,13 +284,13 @@ class SpriteEncoder(object): if filename_8bpp is not None: mask_im = self.open_image_file(filename_8bpp.value) if mask_im.mode != "P": - raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value) + raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value, pos) im_mask_pal = palette.validate_palette(mask_im, filename_8bpp.value) info_byte |= INFO_PAL (im_width, im_height) = mask_im.size if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height: - raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), filename_8bpp.pos) + raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos) mask_sprite = mask_im.crop((mask_x, mask_y, mask_x + size_x, mask_y + size_y)) mask_sprite_data = self.palconvert(mask_sprite.tostring(), im_mask_pal)