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)