def _externalize_anim(anim, animations_path_rel, base_path):
     ensure(anim.get('name', '') != '', 'empty animation name')
     anim_path_rel = '{}/{}.json'.format(animations_path_rel, anim['name'])
     anim_path = '{}/{}'.format(base_path, anim_path_rel)
     with open(anim_path, 'w') as anim_file:
         dict_to_json(anim, anim_file, base_path)
     return {'type': 'import', 'src': anim_path_rel}
Beispiel #2
0
def tile_subrasters(layer):
    """
	Return a list of tiles position in a layer

	layer: A layer as parsed from an ORA file.

	Result type:
		[
			{'x': int, 'y': int},
			...
		]

		Each element in the list is a position, in pixels, from the top-left corner of the layer.
		Each returned position is the top-left pixel of an 8x8 tile.
	"""
    ensure(
        layer['raster'].size[0] % 8 == 0 and layer['raster'].size[1] % 8 == 0,
        'partial tiles in raster of size {}x{}'.format(
            layer['raster'].size[0], layer['raster'].size[1]))

    tile_positions = []
    raster_horizontal_tiles_count = layer['raster'].size[0] // 8
    raster_vertical_tiles_count = layer['raster'].size[1] // 8
    for tile_y_num_in_raster in range(raster_vertical_tiles_count):
        tile_y_pos_in_raster = tile_y_num_in_raster * 8
        for tile_x_num_in_raster in range(raster_horizontal_tiles_count):
            tile_x_pos_in_raster = tile_x_num_in_raster * 8

            tile_positions.append({
                'x': tile_x_pos_in_raster,
                'y': tile_y_pos_in_raster
            })

    return tile_positions
Beispiel #3
0
	def check(self):
		# Self consistency check of characters
		for character in self.characters:
			character.check()

		# Check that no unique name is shared between characters
		character_names = []
		animation_names = []

		def ensure_unique_animation_name(anim):
			ensure(anim.name not in animation_names, 'multiple animations are named "{}"'.format(anim.name))

		for character in self.characters:
			ensure(character.name not in character_names, 'multiple characters are named "{}"'.format(character.name))
			character_names.append(character.name)

			ensure_unique_animation_name(character.victory_animation)
			ensure_unique_animation_name(character.defeat_animation)
			ensure_unique_animation_name(character.menu_select_animation)
			for anim in character.animations:
				ensure_unique_animation_name(anim)

		# Self consistency check of tilesets
		for tileset in self.tilesets:
			tileset.check()

		# Check that all tilesets are uniqueley named and not oversized
		tileset_names = []
		for tileset_index in range(len(self.tilesets)):
			tileset = self.tilesets[tileset_index]
			ensure(tileset.name is not None, 'tileset #{} is not named'.format(tileset_index))
			ensure(re.match('[a-z_][a-z0-9_]+', tileset.name) is not None, 'invalid tileset name "{}" (shall be lower case, numbers and underscores)'.format(tileset.name))
			ensure(tileset.name not in tileset_names, 'multiple tilesets are named "{}"'.format(tileset.name))
			ensure(len(tileset.tiles) <= 256)
			tileset_names.append(tileset.name)
Beispiel #4
0
def _uniq_transparent(color):
    ensure(
        isinstance(color, tuple) and len(color) == 4,
        'expected RGBA images, but seems not to be the case')
    if color[3] == 0:
        return (0, 0, 0, 0)
    else:
        return color
Beispiel #5
0
    def check(self):
        ensure(isinstance(self.input, int) or isinstance(self.input, str))
        if isinstance(self.input, int):
            ensure(0 <= self.input and self.input <= 255)
        if isinstance(self.input, str):
            ensure(self.input[:17] == 'CONTROLLER_INPUT_')

        ensure(isinstance(self.duration, int))
        ensure(0 <= self.duration and self.duration <= 255)
Beispiel #6
0
def place_in_tileset(tile, tileset, tilename):
    """
	Get the name and attributes of a tile in a tileset, append the tile if not already present

	tile: The tile to search for
	tileset: The tileset to search in
	tilename: If the tile is added, it will be given this name

	return a tuple (name, attributes)
		name: The name of the tile found (may differ from tilename)
		attributes: a dictionary {'v': bool, 'h': bool}
			v: True if the returned tile needs to be flipped vertically to match the searched tile
			h: True if the returned tile needs to be flipped horizontally to match the seached tile
	"""
    found_tile_name = None
    flip = None
    for try_flip in [{
            'v': False,
            'h': False
    }, {
            'v': False,
            'h': True
    }, {
            'v': True,
            'h': False
    }, {
            'v': True,
            'h': True
    }]:
        flipped = stblib.tiles.Tile(
            representation=copy.deepcopy(tile._representation))
        if try_flip['v']:
            flipped.flip_v()
        if try_flip['h']:
            flipped.flip_h()
        try:
            found_tile_index = tileset.tiles.index(flipped)
            found_tile_name = tileset.tilenames[found_tile_index]
            flip = try_flip
            break
        except ValueError:
            pass

    if found_tile_name is None:
        found_tile_name = tilename
        flip = {'v': False, 'h': False}
        tileset.tiles.append(tile)
        tileset.tilenames.append(found_tile_name)

    ensure(found_tile_name is not None,
           'internal error: place_in_tileset failed to determine a tilename')
    ensure(
        flip is not None,
        'internal error: place_in_tileset failed to determine tile\'s attributes'
    )
    return (found_tile_name, flip)
def _handle_tileset(obj, base_path):
	if obj.get('src') is not None:
		obj['tiles'] = extract_tiles_from_img('{}/{}'.format(base_path, obj['src']))

		nb_tiles = len(obj['tiles'])
		nb_tilenames = len(obj['tilenames'])
		ensure(nb_tiles >= nb_tilenames, 'less tiles in image "{}" than names in the tileset'.format(obj['src']))
		if nb_tiles > nb_tilenames:
			obj['tiles'] = obj['tiles'][:nb_tilenames]

		del obj['src']
	return obj
Beispiel #8
0
def extract_params(qs, known_params, object_type, object_name):
	"""
	Extract parameters from a query string to a dict while ensuring there is no unknown parameter

	qs: the query string
	known_params: a list of known parameters, any unknown parameter found will fail an "ensure" check
	object_type: string containing the type of object to which the parameters relate (for error message purpose)
	object_name: string containing the name of the object to which the parameters relate (for error message purpose)
	"""
	params = urllib.parse.parse_qs(qs, strict_parsing = True)
	for param_name in params.keys():
		ensure(param_name in known_params, 'unknown {} parameter "{}" for {} {}'.format(object_type, param_name, object_type, object_name))
		ensure(len(params[param_name]) == 1, '{} parameter "{}" defined multiple times in {} {}'.format(object_type, param_name, object_type, object_name))
		params[param_name] = params[param_name][0]
	return params
Beispiel #9
0
def parse_fileobj(f, on_listing=None, on_file=None):
    line_num = 1

    for line in f:
        parsed = parse_line(line)

        ensure(parsed['type'] in ['listing', 'empty', 'filename'])
        if parsed['type'] == 'listing':
            if on_listing is not None:
                on_listing(line_num, parsed['parsed'])
        elif parsed['type'] == 'filename':
            if on_file is not None:
                on_file(line_num, parsed['file'])

        line_num += 1
Beispiel #10
0
    def check(self):
        ensure(is_valid_label_name(self.name))

        ensure(isinstance(self.steps, list))
        ensure(len(self.steps) > 0, 'empty action')
        for step in self.steps:
            ensure(isinstance(step, AiActionStep))
            step.check()
Beispiel #11
0
    def check(self):
        ensure(isinstance(self.action, str))

        ensure(isinstance(self.hitbox, AiHitbox))
        self.hitbox.check()

        ensure(isinstance(self.constraints, int))
        ensure(
            self.constraints <= AiAttack.ConstraintFlag.DIRECTION_LEFT +
            AiAttack.ConstraintFlag.DIRECTION_RIGHT,
            "unknown flag set in action's constraints")
        ensure(
            not (self.constraint_set(AiAttack.ConstraintFlag.DIRECTION_LEFT)
                 and self.constraint_set(
                     AiAttack.ConstraintFlag.DIRECTION_RIGHT)),
            "impossible constraints mix: right and left")
def tileset_to_img(tileset, img_with_in_tiles, img_height_in_tiles):
    # Normalize to accept a Tileset or a list of tiles as input
    tiles = None
    if isinstance(tileset, stblib.tiles.Tileset):
        tiles = tileset.tiles
    else:
        tiles = tileset
    ensure(
        len(tiles) <= img_with_in_tiles * img_height_in_tiles,
        'too much tiles in the tileset: {} / {}'.format(
            len(tiles), img_with_in_tiles * img_height_in_tiles))

    # Create empty image
    img_size = (img_with_in_tiles * 8, img_height_in_tiles * 8)
    img = PIL.Image.new('P', img_size, 0)
    img.putpalette([
        238,
        130,
        238,
        0,
        0,
        0,
        128,
        128,
        128,
        255,
        255,
        255,
    ] + [0] * (256 * 3 - 4 * 3))

    # Draw pixels from tiles data
    tile_num = 0
    for tile in tiles:
        representation = None
        if isinstance(tile, stblib.tiles.Tile):
            representation = tile._representation
        else:
            representation = tile['representation']

        for tile_y in range(8):
            img_y = (int(tile_num / img_with_in_tiles) * 8) + tile_y
            for tile_x in range(8):
                img_x = (tile_num % img_with_in_tiles) * 8 + tile_x
                img.putpixel((img_x, img_y), representation[tile_y][tile_x])
        tile_num += 1

    return img
Beispiel #13
0
def parse_listing_line(line):
    """
	Parse a listing line.

	Warning: not a filename line nor an empty line, just an actual listing line
	"""
    if line[-1] == '\n':
        line = line[:-1]
    ensure(len(line) >= 39, 'listing line truncated "{}"'.format(line))

    return {
        'line': int(line[0:5]),
        'segment': line[6],
        'address': int(line[8:12], 16),
        'data_repr':
        line[14:38],  #TODO parse bytes in "data" field, and deprecate this one
        'code': line[39:]
    }
Beispiel #14
0
def parse_tile(sprite_layer, pos_in_raster, palettes, sprite_container_name):
    """
	Extract an stblib Tile from an ORA layer

	sprite_layer: The layer containing original sprite
	pos_in_raster: {x,y} pixel position of the sprite in the layer
	palettes: List of colors parsed from the palettes ORA layer
	sprite_container_name: Name of the sprite layer's parent (for error message purpose)

	return a tuple (tile, palette_number)
		tile: The parsed tile
		palette_number: The NES palette used to colorize that tile
	"""
    tile = stblib.tiles.Tile()
    palette_num = 0
    for y in range(8):
        y_in_raster = pos_in_raster['y'] + y
        for x in range(8):
            x_in_raster = pos_in_raster['x'] + x

            # Convert pixel data to palette index
            try:
                color_index = palettes.index(
                    _uniq_transparent(sprite_layer['raster'].getpixel(
                        (x_in_raster, y_in_raster))))
            except ValueError:
                ensure(
                    False,
                    'a sprite in {} use a color not found in palettes: sprite "{}", position "{}x{}", color "{}", palettes: {}'
                    .format(
                        sprite_container_name, sprite_layer['name'],
                        x_in_raster, y_in_raster,
                        sprite_layer['raster'].getpixel(
                            (x_in_raster, y_in_raster)), palettes))

            # Store pixel in tile
            tile._representation[y][x] = color_index % 4
            if color_index != 0:
                palette_num = int(color_index / 4)

    return (tile, palette_num)
Beispiel #15
0
	def check(self):
		# Self consistency check of characters
		for character in self.characters:
			character.check()

		# Check that no unique name is shared between characters
		character_names = []
		animation_names = []

		def ensure_unique_animation_name(anim):
			ensure(anim.name not in animation_names, 'multiple animations are named "{}"'.format(anim.name))

		for character in self.characters:
			ensure(character.name not in character_names, 'multiple characters are named "{}"'.format(character.name))
			character_names.append(character.name)

			ensure_unique_animation_name(character.victory_animation)
			ensure_unique_animation_name(character.defeat_animation)
			ensure_unique_animation_name(character.menu_select_animation)
			for anim in character.animations:
				ensure_unique_animation_name(anim)
def main():
    FIRST_AVAILABLE_BANK = 5

    # Parse command line
    if len(sys.argv) < 3 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
        print(
            'Compile a game mod stored in JSON format to Super Tilt Bro. source files'
        )
        print('')
        print('usage: {} game-mod-path super-tilt-bro-path')
        print('')
        return 1

    mod_file = sys.argv[1]
    ensure(os.path.isfile(mod_file), 'file not found: "{}"'.format(mod_file))

    game_dir = sys.argv[2]
    ensure(os.path.isdir(game_dir),
           'directory not found: "{}"'.format(game_dir))
    game_dir = os.path.abspath(game_dir)
    if os.path.basename(game_dir) == 'game':
        gamedir = os.path.dirname(gamedir)
    ensure(
        os.path.isdir('{}/game'.format(game_dir)),
        '"game/" folder not found in source directory "{}"'.format(game_dir))

    # Parse mod
    with open(mod_file, 'r') as f:
        mod_dict = stblib.jsonformat.json_to_dict(f, os.path.dirname(mod_file))
    mod = stblib.dictformat.import_from_dict(mod_dict)
    mod.check()

    # Generate characters
    char_to_bank = {}
    current_bank = FIRST_AVAILABLE_BANK
    for character in mod.characters:
        if character.name not in char_to_bank:
            char_to_bank[character.name] = current_bank
            current_bank += 1

        generate_character(character, game_dir)

    # Generate shared character files
    generate_characters_index(mod.characters, game_dir)

    # Generate tilesets
    tileset_to_bank = {}
    current_bank = FIRST_AVAILABLE_BANK
    for tileset in mod.tilesets:
        tileset_to_bank[tileset.name] = current_bank
        current_bank += 1

        generate_tileset(tileset, game_dir)

    # Generate bank files
    generate_banks(char_to_bank, tileset_to_bank, game_dir)

    return 0
def extract_tiles_from_img(image_file_path):
    tiles = []

    # Open image file and do sanity checks
    img = PIL.Image.open(image_file_path)
    ensure(img.mode == 'P',
           'image file "{}" is not in palette mode'.format(image_file_path))

    width = img.size[0]
    height = img.size[1]
    ensure(
        width % 8 == 0,
        'image file "{}" does not contain 8x8 tiles'.format(image_file_path))
    ensure(
        height % 8 == 0,
        'image file "{}" does not contain 8x8 tiles'.format(image_file_path))

    # Compute useful values
    width_in_tiles = int(width / 8)
    height_in_tiles = int(height / 8)

    # Generate tiles
    for y_tile in range(height_in_tiles):
        for x_tile in range(width_in_tiles):
            tile = {
                'type':
                'tile',
                'representation': [
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0],
                ],
            }
            for y in range(8):
                y_img = y_tile * 8 + y
                for x in range(8):
                    x_img = x_tile * 8 + x
                    tile['representation'][y][x] = img.getpixel(
                        (x_img, y_img)) % 4
            tiles.append(tile)

    return tiles
Beispiel #18
0
def ora_to_character(image_file, char_name):
    character = stblib.character.Character(name=char_name)

    # Read Open Raster file
    image = ora.read_ora(image_file)

    # Find interesting elements
    animations_stack = None
    extra_sprites_stack = None
    illustrations_stack = None
    origin_layer = None
    palettes_layer = None
    for child in image['root']['childs']:
        if child.get('name') == 'anims':
            animations_stack = child
            ensure(animations_stack['type'] == 'stack',
                   '"anims" is not a stack')
        elif child.get('name') == 'extra_sprites':
            extra_sprites_stack = child
            ensure(extra_sprites_stack['type'] == 'stack',
                   '"extra_sprites" is not a stack')
        elif child.get('name') == 'illustrations':
            illustrations_stack = child
            ensure(illustrations_stack['type'] == 'stack',
                   '"illustrations" is not a stack')
        elif child.get('name') == 'origin':
            origin_layer = child
            ensure(origin_layer['type'] == 'layer', '"origin" is not a layer')
        elif child.get('name') == 'palettes':
            palettes_layer = child
            ensure(palettes_layer['type'] == 'layer',
                   '"palettes" is not a layer')

    # Convert origin info to a usable form
    ensure(origin_layer is not None, 'No origin layer found')
    origin = {'x': origin_layer['x'], 'y': origin_layer['y']}

    # Convert palettes info to a usable form
    ensure(palettes_layer is not None, 'No palettes layer found')
    palettes = []
    for color in palettes_layer['raster'].getdata():
        palettes.append(_uniq_transparent(color))

    # Extract illustrations
    ensure(illustrations_stack is not None, 'no illustrations stack found')
    for illustration_layer in illustrations_stack['childs']:
        if illustration_layer['name'] == 'illustrations.small':
            ensure(
                illustration_layer['raster'].size[0] == 16
                and illustration_layer['raster'].size[1] == 16,
                'unnexpected size of {}x{} for small illustration'.format(
                    illustration_layer['raster'].size[0],
                    illustration_layer['raster'].size[1]))
            illustration_id = 'SMALL'
            tileset = character.illustration_small
        elif illustration_layer['name'] == 'illustrations.token':
            ensure(
                illustration_layer['raster'].size[0] == 8
                and illustration_layer['raster'].size[1] == 8,
                'unnexpected size of {}x{} for small illustration'.format(
                    illustration_layer['raster'].size[0],
                    illustration_layer['raster'].size[1]))
            illustration_id = 'TOKEN'
            tileset = character.illustration_token
        else:
            ensure(
                False,
                'uknown illustration "{}"'.format(illustration_layer['name']))

        tileset.tiles = []
        tileset.tilenames = []
        for subraster in tile_subrasters(illustration_layer):
            tile, _ = parse_tile(illustration_layer, subraster, palettes,
                                 illustrations_stack['name'])
            tileset.tiles.append(tile)
            tileset.tilenames.append('{}_ILLUSTRATION_{}_{}'.format(
                character.name.upper(), illustration_id, len(tileset.tiles)))

    # Place extra sprites in tileset
    if extra_sprites_stack is not None:
        for sprite_layer in extra_sprites_stack['childs']:
            ensure(
                sprite_layer['type'] == 'layer',
                'extra_sprites child "{}" is not a layer'.format(
                    sprite_layer['name']))
            ensure(
                re.match('^[A-Z][A-Z0-9_]*$', sprite_layer['name']),
                'invalid extra sprite name "{}"'.format(
                    extra_sprites_stack['childs']))
            ensure(
                sprite_layer['raster'].size[0] == 8
                and sprite_layer['raster'].size[1] == 8,
                'unexpected sprite size of {}x{} in extra_sprites: "{}"'.
                format(sprite_layer['raster'].size[0],
                       sprite_layer['raster'].size[1], sprite_layer['name']))

            tile, _ = parse_tile(sprite_layer, {
                'x': 0,
                'y': 0
            }, palettes, extra_sprites_stack['name'])
            place_in_tileset(
                tile, character.tileset,
                '{}_TILE_{}'.format(character.name.upper(),
                                    sprite_layer['name']))

    # Construct animations and tileset
    for animation_stack in animations_stack['childs']:
        # Parse animation
        animation = stblib.animations.Animation()

        m = re.match('^anims\.(?P<anim>[a-z0-9_]+)$', animation_stack['name'])
        ensure(
            m is not None, 'invalid animation stack name "{}"'.format(
                animation_stack['name']))
        anim_name = m.group('anim')
        animation.name = '{}_anim_{}'.format(character.name, anim_name)

        for frame_stack in animation_stack['childs']:
            frame = stblib.animations.Frame()

            m = re.match(
                '^anims\.(?P<anim>[a-z0-9_]+)\.frame(?P<frame>[0-9]+)(\?(?P<params>.*))?$',
                frame_stack['name'])
            ensure(m is not None,
                   'invalid frame stack name "{}"'.format(frame_stack['name']))
            ensure(
                m.group('anim') == anim_name,
                'frame stack "{}" is named after animation "{}" while in animation "{}"'
                .format(frame_stack['name'], m.group('anim'), anim_name))
            params = {}
            if m.group('params') is not None:
                params = extract_params(m.group('params'), ['dur'], 'frame',
                                        frame_stack['name'])

            frame_id = m.group('frame')
            frame.duration = int(params.get('dur', '4'))

            for frame_child in frame_stack['childs']:
                if frame_child['name'] == 'anims.{}.frame{}.hurtbox'.format(
                        anim_name, frame_id):
                    # Parse hurtbox
                    ensure(frame_child['type'] == 'layer',
                           '{} is note a layer'.format(frame_child['name']))

                    hurtbox = stblib.animations.Hurtbox(
                        left=frame_child['x'] - origin['x'],
                        top=frame_child['y'] - origin['y'])
                    hurtbox.right = hurtbox.left + frame_child['raster'].size[
                        0]  #TODO check if we should substract 1 (so that a 16 pixels wide box at 0 ends on pixel 15)
                    hurtbox.bottom = hurtbox.top + frame_child['raster'].size[
                        1]  #TODO check if we should substract 1
                    frame.hurtbox = hurtbox

                elif remove_qs(frame_child['name']
                               ) == 'anims.{}.frame{}.hitbox'.format(
                                   anim_name, frame_id):
                    # Parse hitbox
                    ensure(
                        frame.hitbox is None,
                        'multiple hitboxes defined for frame anims.{}.frame{}'.
                        format(anim_name, frame_id))

                    hitbox_qs = urllib.parse.urlparse(
                        frame_child['name']).query
                    ensure(
                        hitbox_qs != '', 'hitbox {} has no parameters'.format(
                            frame_child['name']))
                    hitbox_params = extract_params(
                        hitbox_qs, [
                            'enabled', 'damages', 'base_h', 'force_h',
                            'base_v', 'force_v'
                        ], 'hitbox', remove_qs(frame_child['name']))

                    hitbox = stblib.animations.Hitbox(
                        enabled=hitbox_params.get('enabled',
                                                  'false') == 'true',
                        damages=int(hitbox_params.get('damages', '0')),
                        base_h=int(hitbox_params.get('base_h', '0')),
                        base_v=int(hitbox_params.get('base_v', '0')),
                        force_h=int(hitbox_params.get('force_h', '0')),
                        force_v=int(hitbox_params.get('force_v', '0')),
                        left=frame_child['x'] - origin['x'],
                        top=frame_child['y'] - origin['y'])
                    hitbox.right = hitbox.left + frame_child['raster'].size[
                        0]  #TODO check if we should substract 1
                    hitbox.bottom = hitbox.top + frame_child['raster'].size[
                        1]  #TODO check if we should substract 1

                    frame.hitbox = hitbox

                elif frame_child['name'] == 'anims.{}.frame{}.sprites'.format(
                        anim_name, frame_id):
                    # Parse sprites
                    ensure(frame_child['type'] == 'stack',
                           '{} is not a stack'.format(frame_child['name']))

                    # Collect sprite layers
                    sprite_layers = []
                    for sprites_container_child in frame_child['childs']:
                        if sprites_container_child['type'] == 'layer':
                            sprite_layers.append({
                                'foreground':
                                False,
                                'layer':
                                sprites_container_child
                            })
                        elif sprites_container_child['type'] == 'stack':
                            ensure(
                                sprites_container_child['name'] ==
                                'anims.{}.frame{}.sprites.foreground'.format(
                                    anim_name, frame_id),
                                'unexpected stack in {}: "{}"'.format(
                                    frame_child['name'],
                                    sprites_container_child['name']))
                            for foreground_sprite_layer in sprites_container_child[
                                    'childs']:
                                ensure(
                                    foreground_sprite_layer['type'] == 'layer',
                                    'unexpected non-layer in {}: "{}"'.format(
                                        sprites_container_child['name'],
                                        sprite_layer['name']))
                                sprite_layers.append({
                                    'foreground':
                                    True,
                                    'layer':
                                    foreground_sprite_layer
                                })
                        else:
                            ensure(
                                False,
                                'unexpected non-stack non-layer in {}: "{}"'.
                                format(frame_child['name'],
                                       sprites_container_child['name']))

                    # Parse sprite layers
                    for sprite_layer_info in sprite_layers:
                        sprite_layer = sprite_layer_info['layer']
                        ensure(
                            sprite_layer['type'] == 'layer',
                            'unexpected non-layer in {}: "{}"'.format(
                                frame_child['name'], sprite_layer['name']))
                        ensure(
                            sprite_layer['raster'].size[0] % 8 == 0
                            and sprite_layer['raster'].size[1] % 8 == 0,
                            'unexpected sprite size of {}x{} in {}: "{}"'.
                            format(sprite_layer['raster'].size[0],
                                   sprite_layer['raster'].size[1],
                                   frame_child['name'], sprite_layer['name']))

                        for tile_pos_in_raster in tile_subrasters(
                                sprite_layer):
                            # Parse tile
                            tile, palette_num = parse_tile(
                                sprite_layer, tile_pos_in_raster, palettes,
                                frame_child['name'])

                            # Add tile to tileset if needed, get its name and attributes
                            tile_name, flip = place_in_tileset(
                                tile, character.tileset, '{}_TILE_{}'.format(
                                    character.name.upper(),
                                    len(character.tileset.tiles)))

                            # Add sprite to frame
                            numeric_attr = palette_num
                            if flip['v']:
                                numeric_attr += 0x80
                            if flip['h']:
                                numeric_attr += 0x40
                            sprite = stblib.animations.Sprite(
                                y=sprite_layer['y'] + tile_pos_in_raster['y'] -
                                origin['y'],
                                tile=tile_name,
                                attr=numeric_attr,
                                x=sprite_layer['x'] + tile_pos_in_raster['x'] -
                                origin['x'],
                                foreground=sprite_layer_info['foreground'])
                            frame.sprites.append(sprite)
                else:
                    # Refuse unknown children in a frame, it is certainly a naming error or something not yet supported
                    ensure(
                        False, 'unknown frame child in {}: "{}"'.format(
                            frame_stack['name'], frame_child['name']))

            animation.frames.append(frame)

        # Store parsed animation in character
        if anim_name == 'victory':
            character.victory_animation = animation
        elif anim_name == 'defeat':
            character.defeat_animation = animation
        elif anim_name == 'menu_select':
            character.menu_select_animation = animation
        else:
            character.animations.append(animation)

    character.animations = sorted(character.animations, key=lambda x: x.name)
    return character
Beispiel #19
0
	def check(self):
		ensure(isinstance(self.primary_names, list))
		for name in self.primary_names:
			ensure(isinstance(name, str))
			ensure(len(name) >= 1)
			ensure(len(name) <= 8)

		ensure(isinstance(self.secondary_names, list))
		for name in self.secondary_names:
			ensure(isinstance(name, str))
			ensure(len(name) >= 1)
			ensure(len(name) <= 8)

		ensure(isinstance(self.primary_colors, list))
		for colors in self.primary_colors:
			ensure(isinstance(colors, Palette))
			colors.check()

		ensure(isinstance(self.alternate_colors, list))
		for colors in self.alternate_colors:
			ensure(isinstance(colors, Palette))
			colors.check()

		ensure(isinstance(self.secondary_colors, list))
		for colors in self.secondary_colors:
			ensure(isinstance(colors, Palette))
			colors.check()

		ensure(len(self.primary_names) == len(self.primary_colors))
		ensure(len(self.alternate_colors) == len(self.primary_colors))
		ensure(len(self.secondary_names) == len(self.secondary_colors))
Beispiel #20
0
	def check(self):
		ensure(len(self.name) > 0)
		ensure(self.start_routine is None or len(self.start_routine) > 0)
		ensure(len(self.update_routine) > 0)
		ensure(len(self.offground_routine) > 0)
		ensure(len(self.onground_routine) > 0)
		ensure(len(self.input_routine) > 0)
		ensure(len(self.onhurt_routine) > 0)
Beispiel #21
0
# Parse command line
perf_filename = sys.argv[1]
listing_filename = sys.argv[2]

# Gather statistics per instruction
cycles_per_instr = {}
cycles_total = 0
re_perf = re.compile(
    '^(?P<stack>([0-9a-f]{4};)*)?(?P<instr>[0-9a-f]{4}) (?P<cycles>[0-9]+)$')
with open(perf_filename, 'r') as perf_file:
    for line in perf_file:
        if line[-1] == '\n':
            line = line[:-1]

        m = re_perf.match(line)
        ensure(m is not None, "invalid line in input file: '{}'".format(line))
        instr = int(m.group('instr'), 16)
        cycles = int(m.group('cycles'))

        if instr >= 0xc000:
            cycles_per_instr[instr] = cycles_per_instr.get(instr, 0) + cycles
            cycles_total += cycles


# Anotate listing file
def o(m):
    print(m)


def on_file(line_num, filename):
    o('')
Beispiel #22
0
	def check(self):
		ensure(isinstance(self.tiles, list))
		for tile in self.tiles:
			ensure(isinstance(tile, Tile))
			tile.check()

		ensure(isinstance(self.tilenames, list))
		for tilename in self.tilenames:
			ensure(isinstance(tilename, str))
			ensure(len(tilename) > 0)

		ensure(len(self.tilenames) == len(self.tiles))
Beispiel #23
0
	def check(self):
		ensure(isinstance(self._representation, list))
		ensure(len(self._representation) == 8)
		for line in self._representation:
			ensure(isinstance(line, list))
			ensure(len(line) == 8)
			for pixel in line:
				ensure(isinstance(pixel, int))
				ensure(pixel >= 0)
				ensure(pixel <= 3)
Beispiel #24
0
def ora_to_character(image_file, char_name):
	character = stblib.character.Character(name = char_name)

	# Read Open Raster file
	image = ora.read_ora(image_file)

	# Find interesting elements
	animations_stack = None
	origin_layer = None
	palettes_layer = None
	for child in image['root']['childs']:
		if child.get('name') == 'anims':
			animations_stack = child
			ensure(animations_stack['type'] == 'stack', '"anims" is not a stack')
		elif child.get('name') == 'origin':
			origin_layer = child
			ensure(origin_layer['type'] == 'layer', '"origin" is not a layer')
		elif child.get('name') == 'palettes':
			palettes_layer = child
			ensure(palettes_layer['type'] == 'layer', '"palettes" is not a layer')

	# Convert origin info to a usable form
	ensure(origin_layer is not None, 'No origin layer found')
	origin = {
		'x': origin_layer['x'],
		'y': origin_layer['y']
	}

	# Convert palettes info to a usable form
	ensure(palettes_layer is not None, 'No palettes layer found')
	palettes = []
	for color in palettes_layer['raster'].getdata():
		palettes.append(_uniq_transparent(color))

	# Construct animations and tileset
	for animation_stack in animations_stack['childs']:
		# Parse animation
		animation = stblib.animations.Animation()

		m = re.match('^anims\.(?P<anim>[a-z0-9_]+)$', animation_stack['name'])
		ensure(m is not None, 'invalid animation stack name "{}"'.format(animation_stack['name']))
		anim_name = m.group('anim')
		animation.name = '{}_anim_{}'.format(character.name, anim_name)

		for frame_stack in animation_stack['childs']:
			frame = stblib.animations.Frame()

			m = re.match('^anims\.(?P<anim>[a-z0-9_]+)\.frame(?P<frame>[0-9]+)(\?(?P<params>.*))?$', frame_stack['name'])
			ensure(m is not None, 'invalid frame stack name "{}"'.format(frame_stack['name']))
			ensure(m.group('anim') == anim_name, 'frame stack "{}" is named after animation "{}" while in animation "{}"'.format(frame_stack['name'], m.group('anim'), anim_name))
			params = {}
			if m.group('params') is not None:
				params = extract_params(m.group('params'), ['dur'], 'frame', frame_stack['name'])

			frame_id = m.group('frame')
			frame.duration = int(params.get('dur', '4'))

			for frame_child in frame_stack['childs']:
				if frame_child['name'] == 'anims.{}.frame{}.hurtbox'.format(anim_name, frame_id):
					# Parse hurtbox
					ensure(frame_child['type'] == 'layer', '{} is note a layer'.format(frame_child['name']))

					hurtbox = stblib.animations.Hurtbox(
						left = frame_child['x'] - origin['x'],
						top = frame_child['y'] - origin['y']
					)
					hurtbox.right = hurtbox.left + frame_child['raster'].size[0]
					hurtbox.bottom = hurtbox.top + frame_child['raster'].size[1]
					frame.hurtbox = hurtbox

				elif remove_qs(frame_child['name']) == 'anims.{}.frame{}.hitbox'.format(anim_name, frame_id):
					# Parse hitbox
					ensure(frame.hitbox is None, 'multiple hitboxes defined for frame anims.{}.frame{}'.format(anim_name, frame_id))

					hitbox_qs = urllib.parse.urlparse(frame_child['name']).query
					ensure(hitbox_qs != '', 'hitbox {} has no parameters'.format(frame_child['name']))
					hitbox_params = extract_params(
						hitbox_qs,
						['enabled', 'damages', 'base_h', 'force_h', 'base_v', 'force_v'],
						'hitbox', remove_qs(frame_child['name'])
					)

					hitbox = stblib.animations.Hitbox(
						enabled = hitbox_params.get('enabled', 'false') == 'true',
						damages = int(hitbox_params.get('damages', '0')),
						base_h = int(hitbox_params.get('base_h', '0')),
						base_v = int(hitbox_params.get('base_v', '0')),
						force_h = int(hitbox_params.get('force_h', '0')),
						force_v = int(hitbox_params.get('force_v', '0')),
						left = frame_child['x'] - origin['x'],
						top = frame_child['y'] - origin['y']
					)
					hitbox.right = hitbox.left + frame_child['raster'].size[0]
					hitbox.bottom = hitbox.top + frame_child['raster'].size[1]

					frame.hitbox = hitbox

				elif frame_child['name'] == 'anims.{}.frame{}.sprites'.format(anim_name, frame_id):
					# Parse sprites
					ensure(frame_child['type'] == 'stack', '{} is not a stack'.format(frame_child['name']))

					# Collect sprite layers
					sprite_layers = []
					for sprites_container_child in frame_child['childs']:
						if sprites_container_child['type'] == 'layer':
							sprite_layers.append({'foreground': False, 'layer': sprites_container_child})
						elif sprites_container_child['type'] == 'stack':
							ensure(sprites_container_child['name'] == 'anims.{}.frame{}.sprites.foreground'.format(anim_name, frame_id), 'unexpected stack in {}: "{}"'.format(frame_child['name'], sprite_layer['name']))
							for foreground_sprite_layer in sprites_container_child['childs']:
								ensure(foreground_sprite_layer['type'] == 'layer', 'unexpected non-layer in {}: "{}"'.format(sprites_container_child['name'], sprite_layer['name']))
								sprite_layers.append({'foreground': True, 'layer': foreground_sprite_layer})
						else:
							ensure(False, 'unexpected non-stack non-layer in {}: "{}"'.format(frame_child['name'], sprites_container_child['name']))

					# Parse sprite layers
					for sprite_layer_info in sprite_layers:
						sprite_layer = sprite_layer_info['layer']
						ensure(sprite_layer['type'] == 'layer', 'unexpected non-layer in {}: "{}"'.format(frame_child['name'], sprite_layer['name']))
						ensure(sprite_layer['raster'].size[0] % 8 == 0 and sprite_layer['raster'].size[1] % 8 == 0, 'unexpected sprite size of {}x{} in {}: "{}"'.format(
							sprite_layer['raster'].size[0], sprite_layer['raster'].size[1],
							frame_child['name'], sprite_layer['name']
						))

						raster_horizontal_tiles_count = sprite_layer['raster'].size[0] // 8
						raster_vertical_tiles_count = sprite_layer['raster'].size[1] // 8
						for tile_y_num_in_raster in range(raster_vertical_tiles_count):
							tile_y_pos_in_raster = tile_y_num_in_raster * 8
							for tile_x_num_in_raster in range(raster_horizontal_tiles_count):
								tile_x_pos_in_raster = tile_x_num_in_raster * 8

								# Parse tile
								tile = stblib.tiles.Tile()
								palette_num = 0
								for y in range(8):
									y_in_raster = tile_y_pos_in_raster + y
									for x in range(8):
										x_in_raster = tile_x_pos_in_raster + x

										# Convert pixel data to palette index
										try:
											color_index = palettes.index(_uniq_transparent(sprite_layer['raster'].getpixel((x_in_raster, y_in_raster))))
										except ValueError:
											ensure(False, 'a sprite in {} use a color not found in palettes: sprite "{}", position "{}x{}", color "{}", palettes: {}'.format(frame_child['name'], sprite_layer['name'], x_in_raster, y_in_raster, sprite_layer['raster'].getpixel((x_in_raster, y_in_raster)), palettes))

										# Store pixel in tile
										tile._representation[y][x] = color_index % 4
										if color_index != 0:
											palette_num = int(color_index / 4)

								# Add tile to tileset if needed, get its name and attributes
								tile_name = None
								flip = None
								for try_flip in [{'v': False, 'h': False}, {'v': False, 'h': True}, {'v': True, 'h': False}, {'v': True, 'h': True}]:
									flipped = stblib.tiles.Tile(representation = copy.deepcopy(tile._representation))
									if try_flip['v']:
										flipped.flip_v()
									if try_flip['h']:
										flipped.flip_h()
									try:
										tile_index = character.tileset.tiles.index(flipped)
										tile_name = character.tileset.tilenames[tile_index]
										flip = try_flip
										break
									except ValueError:
										pass

								if tile_name is None:
									tile_name = '{}_TILE_{}'.format(character.name.upper(), len(character.tileset.tiles))
									flip = {'v': False, 'h': False}
									character.tileset.tiles.append(tile)
									character.tileset.tilenames.append(tile_name)

								# Add sprite to frame
								numeric_attr = palette_num
								if flip['v']:
									numeric_attr += 0x80
								if flip['h']:
									numeric_attr += 0x40
								sprite = stblib.animations.Sprite(
									y = sprite_layer['y'] + tile_y_pos_in_raster - origin['y'],
									tile = tile_name,
									attr = numeric_attr,
									x = sprite_layer['x'] + tile_x_pos_in_raster - origin['x'],
									foreground = sprite_layer_info['foreground']
								)
								frame.sprites.append(sprite)
				else:
					# Refuse unknown children in a frame, it is certainly a naming error or something not yet supported
					ensure(False, 'unknown frame child in {}: "{}"'.format(frame_stack['name'], frame_child['name']))

			animation.frames.append(frame)

		# Store parsed animation in character
		if anim_name == 'victory':
			character.victory_animation = animation
		elif anim_name == 'defeat':
			character.defeat_animation = animation
		elif anim_name == 'menu_select':
			character.menu_select_animation = animation
		else:
			character.animations.append(animation)

	character.animations = sorted(character.animations, key = lambda x: x.name)
	return character
Beispiel #25
0
	def check(self):
		ensure(isinstance(self.colors, list))
		ensure(len(self.colors) == 3)
		for color in self.colors:
			ensure(color >= 0)
			ensure(color <= 255)
Beispiel #26
0
		def ensure_unique_animation_name(anim):
			ensure(anim.name not in animation_names, 'multiple animations are named "{}"'.format(anim.name))
def _jsonify_character(character, base_path):
	character_path_rel = 'characters/{}'.format(character['name'])
	animations_path_rel = '{}/animations'.format(character_path_rel)
	illustrations_path_rel = '{}/illustrations'.format(character_path_rel)
	character_path = '{}/{}'.format(base_path, character_path_rel)
	animations_path = '{}/{}'.format(base_path, animations_path_rel)
	illustrations_path = '{}/{}'.format(base_path, illustrations_path_rel)
	os.makedirs(character_path, exist_ok=True)
	os.makedirs(animations_path, exist_ok=True)

	# Convert tilesets to gif based tileset
	tileset_src = '{}/tileset.gif'.format(character_path)
	tileset_img = tileset_to_img(character['tileset']['tiles'], 8, 12)
	tileset_img.save(tileset_src)
	del character['tileset']['tiles']
	character['tileset']['src'] = '{}/tileset.gif'.format(character_path_rel)

	illustration_small_src = '{}/small.gif'.format(illustrations_path)
	illustration_small_img = tileset_to_img(character['illustration_small']['tiles'], 2, 2)
	illustration_small_img.save(illustration_small_src)
	del character['illustration_small']['tiles']
	character['illustration_small']['src'] = '{}/small.gif'.format(illustrations_path_rel)

	illustration_token_src = '{}/token.gif'.format(illustrations_path)
	illustration_token_img = tileset_to_img(character['illustration_token']['tiles'], 1, 1)
	illustration_token_img.save(illustration_token_src)
	del character['illustration_token']['tiles']
	character['illustration_token']['src'] = '{}/token.gif'.format(illustrations_path_rel)

	# Export animations in their own file
	def _externalize_anim(anim, animations_path_rel, base_path):
		ensure(anim.get('name', '') != '', 'empty animation name')
		anim_path_rel = '{}/{}.json'.format(animations_path_rel, anim['name'])
		anim_path = '{}/{}'.format(base_path, anim_path_rel)
		with open(anim_path, 'w') as anim_file:
			dict_to_json(anim, anim_file, base_path)
		return {
			'type': 'import',
			'src': anim_path_rel
		}

	for anim_index in range(len(character['animations'])):
		character['animations'][anim_index] = _externalize_anim(character['animations'][anim_index], animations_path_rel, base_path)
	character['victory_animation'] = _externalize_anim(character['victory_animation'], animations_path_rel, base_path)
	character['defeat_animation'] = _externalize_anim(character['defeat_animation'], animations_path_rel, base_path)
	character['menu_select_animation'] = _externalize_anim(character['menu_select_animation'], animations_path_rel, base_path)

	# Export sourcecode in its own file
	source_path_rel = '{}/states.asm'.format(character_path_rel)
	source_path = '{}/{}'.format(base_path, source_path_rel)
	with open(source_path, 'w') as source_file:
		source_file.write(character['sourcecode'])

	del character['sourcecode']
	character['sourcecode_file'] = source_path_rel

	# Export AI's sourcecode in its own file
	ensure(isinstance(character.get('ai'), dict), 'no AI defined for character {}'.format(character['name']))
	if character['ai'].get('sourcecode', '') == '':
		character['ai']['sourcecode_file'] = None
	else:
		source_path_rel = '{}/ai.asm'.format(character_path_rel)
		source_path = '{}/{}'.format(base_path, source_path_rel)
		with open(source_path, 'w') as source_file:
			source_file.write(character['ai']['sourcecode'])

		del character['ai']['sourcecode']
		character['ai']['sourcecode_file'] = source_path_rel

	return character
Beispiel #28
0
	def check(self):
		ensure(isinstance(self.name, str))
		ensure(len(self.name) >= 1)
		ensure(len(self.name) <= 10)

		ensure(isinstance(self.weapon_name, str))
		ensure(len(self.weapon_name) >= 1)
		ensure(len(self.weapon_name) <= 10)

		ensure(isinstance(self.sourcecode, str))

		ensure(isinstance(self.victory_animation, stblib.animations.Animation))
		for frame in self.victory_animation.frames:
			ensure(frame.hitbox is None)
			ensure(frame.hurtbox is None)

		ensure(isinstance(self.defeat_animation, stblib.animations.Animation))
		for frame in self.defeat_animation.frames:
			ensure(frame.hitbox is None)
			ensure(frame.hurtbox is None)

		ensure(isinstance(self.menu_select_animation, stblib.animations.Animation))
		for frame in self.menu_select_animation.frames:
			ensure(frame.hitbox is None)
			ensure(frame.hurtbox is None)

		ensure(isinstance(self.animations, list))
		for animation in self.animations:
			ensure(isinstance(animation, stblib.animations.Animation))

		ensure(isinstance(self.color_swaps, Colorswaps))
		self.color_swaps.check()

		ensure(isinstance(self.states, list))
		for state in self.states:
			ensure(isinstance(state, State))
			state.check()
Beispiel #29
0
	def check(self):
		ensure(-128 <= self.left and self.left <= 127)
		ensure(-128 <= self.right and self.right <= 127)
		ensure(-128 <= self.top and self.top <= 127)
		ensure(-128 <= self.bottom and self.bottom <= 127)
Beispiel #30
0
def generate_character(char, game_dir):
	name_upper = char.name.upper()

	# Create destination directories
	rel_char_dir = 'game/data/characters/{}'.format(char.name)
	rel_anim_dir = '{}/animations'.format(rel_char_dir)
	char_dir = '{}/{}'.format(game_dir, rel_char_dir)
	anim_dir = '{}/{}'.format(game_dir, rel_anim_dir)
	os.makedirs(char_dir)
	os.makedirs(anim_dir)

	# Label names that are used in multiple places
	tileset_label_name = '{}_chr_tiles'.format(char.name)
	illustrations_label_name = '{}_chr_illustrations'.format(char.name)
	primary_palettes_label_name = '{}_character_palettes'.format(char.name)
	alternate_palettes_label_name = '{}_character_alternate_palettes'.format(char.name)
	weapon_palettes_label_name = '{}_weapon_palettes'.format(char.name)
	character_names_label_name = '{}_character_names'.format(char.name)
	weapon_names_label_name = '{}_weapon_names'.format(char.name)
	properties_table_label_name = '{}_properties'.format(char.name)
	ai_attacks_table_label_name = '{}_ai_attacks'.format(char.name)
	ai_selectors_table_label_name = '{}_ai_selectors'.format(char.name)

	# Create character's master file
	master_file_path = '{}/{}.asm'.format(char_dir, char.name)
	with open(master_file_path, 'w') as master_file:
		master_file.write(textwrap.dedent("""\
			{name_upper}_BANK_NUMBER = CURRENT_BANK_NUMBER

			#include "{rel_char_dir}/chr_tiles.asm"
			#include "{rel_char_dir}/chr_illustrations.asm"
			#include "{rel_char_dir}/animations/animations.asm"
			#include "{rel_char_dir}/character_colors.asm"
			#include "{rel_char_dir}/properties.asm"
			#include "{rel_char_dir}/state_events.asm"
			#include "{rel_char_dir}/player_states.asm"
			#include "{rel_char_dir}/ai_data.asm"
			#include "{rel_char_dir}/ai.asm"
		""".format_map(locals())))

	# Tileset file
	chr_tiles_file_path = '{}/chr_tiles.asm'.format(char_dir)
	with open(chr_tiles_file_path, 'w') as chr_tiles_file:
		# Tileset label
		chr_tiles_file.write('{}:\n\n'.format(tileset_label_name))

		# Tiles in binary form, each with a label containing its index
		index_expression = '(*-{})/16'.format(tileset_label_name)
		for tile_index in range(len(char.tileset.tilenames)):
			tile = char.tileset.tiles[tile_index]
			tile_name = char.tileset.tilenames[tile_index]

			# Label containing tile's index
			chr_tiles_file.write('{} = {}\n'.format(tile_name, index_expression))

			# Tile data
			chr_tiles_file.write('{}\n\n'.format(stblib.asmformat.tiles.tile_to_asm(tile)))

		# Tileset footer
		chr_tiles_file.write(textwrap.dedent("""\
			{name_upper}_SPRITE_TILES_NUMBER = {index_expression}
			#print {name_upper}_SPRITE_TILES_NUMBER
			#if {name_upper}_SPRITE_TILES_NUMBER > 96
			#error too many sprites for character {name_upper}
			#endif
		""".format_map(locals())))

	# Illustrations file
	chr_illustrations_file_path = '{}/chr_illustrations.asm'.format(char_dir)
	with open(chr_illustrations_file_path, 'w') as chr_illustrations_file:
		# Illustrations label
		chr_illustrations_file.write('{}:\n\n'.format(illustrations_label_name))
		index_expression = '(*-{})/16'.format(illustrations_label_name)

		# Token illustration
		chr_illustrations_file.write(';\n; Token\n;\n\n')
		ensure(len(char.illustration_token.tilenames) == 1)
		ensure(len(char.illustration_token.tiles) == len(char.illustration_token.tilenames))
		chr_illustrations_file.write('{}\n\n'.format(stblib.asmformat.tiles.tile_to_asm(char.illustration_token.tiles[0])))

		# Small illustration
		chr_illustrations_file.write(';\n; Small\n;\n\n')
		ensure(len(char.illustration_small.tilenames) == 4)
		ensure(len(char.illustration_small.tiles) == len(char.illustration_small.tilenames))
		for tile_index in range(len(char.illustration_small.tilenames)):
			tile = char.illustration_small.tiles[tile_index]
			chr_illustrations_file.write('{}\n\n'.format(stblib.asmformat.tiles.tile_to_asm(tile)))

		# Large illustration
		chr_illustrations_file.write(';\n; Large\n;\n\n')
		ensure(len(char.illustration_large.tilenames) == 48)
		ensure(len(char.illustration_large.tiles) == len(char.illustration_large.tilenames))
		for tile_index in range(len(char.illustration_large.tilenames)):
			tile = char.illustration_large.tiles[tile_index]
			chr_illustrations_file.write('{}\n\n'.format(stblib.asmformat.tiles.tile_to_asm(tile)))

		# Illustration footer
		chr_illustrations_file.write(textwrap.dedent("""\
			{name_upper}_ILLUSTRATION_TILES_NUMBER = {index_expression}
			#print {name_upper}_ILLUSTRATION_TILES_NUMBER
			#if {name_upper}_ILLUSTRATION_TILES_NUMBER <> 53
			#error bad count of illustration tiles for character {name_upper}
			#endif
		""".format_map(locals())))

	# Palettes file
	character_colors_file_path = '{}/character_colors.asm'.format(char_dir)
	with open(character_colors_file_path, 'w') as character_colors_file:
		def write_palettes_table(palettes, label_name, description):
			def _c(i):
				return stblib.utils.intasm8(palette.colors[i])
			character_colors_file.write('; {}\n'.format(description))
			character_colors_file.write('{}:\n'.format(label_name))
			for palette in palettes:
				character_colors_file.write('.byt {}, {}, {}\n'.format(_c(0), _c(1), _c(2)))
			character_colors_file.write('\n')

		def write_palette_names_table(names, label_name, description):
			character_colors_file.write('; {}\n'.format(description))
			character_colors_file.write('{}:\n'.format(label_name))
			for name in names:
				character_colors_file.write('.byt {} ; {}\n'.format(text_asm(name, 8, 2), name))
			character_colors_file.write('\n')

		# Primary palettes
		write_palettes_table(
			char.color_swaps.primary_colors,
			primary_palettes_label_name,
			'Main palette for character'
		)

		# Alternate palettes
		write_palettes_table(
			char.color_swaps.alternate_colors,
			alternate_palettes_label_name,
			'Alternate palette to use to reflect special state'
		)

		# Primary palettes names
		write_palette_names_table(
			char.color_swaps.primary_names,
			character_names_label_name,
			'Character palette name'
		)

		# Secondary palettes
		write_palettes_table(
			char.color_swaps.secondary_colors,
			weapon_palettes_label_name,
			'Secondary palette for character'
		)

		# Secondary palettes names
		write_palette_names_table(
			char.color_swaps.secondary_names,
			weapon_names_label_name,
			'Weapon palette name'
		)

	# Character properties
	properties_file_path = '{}/properties.asm'.format(char_dir)
	with open(properties_file_path, 'w') as properties_file:
		# Propeties table's label
		properties_file.write('{}:\n'.format(properties_table_label_name))

		# Standard animations
		properties_file.write('VECTOR({})\n'.format(char.victory_animation.name))
		properties_file.write('VECTOR({})\n'.format(char.defeat_animation.name))
		properties_file.write('VECTOR({})\n'.format(char.menu_select_animation.name))

		# Character name
		properties_file.write('.byt {} ; {}\n'.format(text_asm(char.name, 10, 2), char.name))
		properties_file.write('.byt {} ; {}\n'.format(text_asm(char.weapon_name, 10, 2), char.weapon_name))

		# Illustrations
		properties_file.write('VECTOR({}) ; Illustrations begining\n'.format(illustrations_label_name))

		# AI
		properties_file.write('VECTOR({}) ; AI selectors\n'.format(ai_selectors_table_label_name))
		properties_file.write('.byt {} ; Number of AI attacks\n'.format(len(char.ai.attacks)))
		properties_file.write('VECTOR({}) ; AI attacks\n'.format(ai_attacks_table_label_name))

	# State events
	state_events_file_path = '{}/state_events.asm'.format(char_dir)
	with open(state_events_file_path, 'w') as state_events_file:
		# State count
		state_events_file.write('{}_NUM_STATES = {}\n\n'.format(name_upper, len(char.states)))

		# Routines tables
		for routine_type in ['start', 'update', 'offground', 'onground', 'input', 'onhurt']:
			state_events_file.write('{}_state_{}_routines:\n'.format(char.name, routine_type))
			for state in char.states:
				routine_name = getattr(state, '{}_routine'.format(routine_type))
				ensure(routine_name is not None or routine_type == 'start', 'in {}\'s state {}, missing {} routine'.format(char.name, state.name, routine_type))

				if routine_name is not None:
					state_events_file.write('STATE_ROUTINE({}) ; {}\n'.format(routine_name, state.name))
			state_events_file.write('\n')

	# Character's logic
	player_states_file_path = '{}/player_states.asm'.format(char_dir)
	with open(player_states_file_path, 'w') as player_states_file:
		player_states_file.write(char.sourcecode)

	# Animations
	rel_animations_path = []
	def write_animation_file(anim):
		rel_anim_file_path = '{}/{}.asm'.format(rel_anim_dir, anim.name)
		anim_file_path = '{}/{}'.format(game_dir, rel_anim_file_path)

		with open(anim_file_path, 'w') as anim_file:
			anim_file.write(stblib.asmformat.animations.animation_to_asm(anim))
		rel_animations_path.append(rel_anim_file_path)

	write_animation_file(char.victory_animation)
	write_animation_file(char.defeat_animation)
	write_animation_file(char.menu_select_animation)
	for anim in char.animations:
		write_animation_file(anim)

	master_animations_file_path = '{}/animations.asm'.format(anim_dir)
	with open(master_animations_file_path, 'w') as master_animations_file:
		for rel_anim_file_path in rel_animations_path:
			master_animations_file.write('#include "{}"\n'.format(rel_anim_file_path))

	# AI
	custom_ai_file_path = '{}/ai.asm'.format(char_dir)
	with open(custom_ai_file_path, 'w') as custom_ai_file:
		custom_ai_file.write(char.ai.sourcecode)

	ai_data_file_path = '{}/ai_data.asm'.format(char_dir)
	with open(ai_data_file_path, 'w') as ai_data_file:
		# Attacks
		ai_data_file.write('{}:\n'.format(ai_attacks_table_label_name))
		ai_data_file.write('; LSBs\n')
		for attack in char.ai.attacks:
			ai_data_file.write('AI_ATTACK_HITBOX({}, ${:02x}, ${:02x}, ${:02x}, ${:02x})\n'.format(
				stblib.utils.uint16lsb(attack.constraints),
				stblib.utils.int16lsb(attack.hitbox.left),
				stblib.utils.int16lsb(attack.hitbox.right),
				stblib.utils.int16lsb(attack.hitbox.top),
				stblib.utils.int16lsb(attack.hitbox.bottom)
			))
			ai_data_file.write('.byt <{}\n'.format(attack.action))
		ai_data_file.write('; MSBs\n')
		for attack in char.ai.attacks:
			ai_data_file.write('AI_ATTACK_HITBOX({}, ${:02x}, ${:02x}, ${:02x}, ${:02x})\n'.format(
				stblib.utils.uint16msb(attack.constraints),
				stblib.utils.int16msb(attack.hitbox.left),
				stblib.utils.int16msb(attack.hitbox.right),
				stblib.utils.int16msb(attack.hitbox.top),
				stblib.utils.int16msb(attack.hitbox.bottom)
			))
			ai_data_file.write('.byt >{}\n'.format(attack.action))
		ai_data_file.write('\n')

		# Selectors
		ai_data_file.write('{}:\n'.format(ai_selectors_table_label_name))
		for selector in char.ai.action_selectors:
			ai_data_file.write('VECTOR({})\n'.format(selector))
		ai_data_file.write('\n')

		# Actions
		for action in char.ai.actions:
			ai_data_file.write('{}_ai_action_{}:\n'.format(char.name, action.name))
			for step in action.steps:
				ai_data_file.write('AI_ACTION_STEP({}, {})\n'.format(step.input, step.duration))
			ai_data_file.write('AI_ACTION_END_STEPS\n')