Mercurial > touhou
changeset 69:a142e57218a0
Refactor. Move VMs to pytouhou.vm.
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Sat, 27 Aug 2011 10:58:54 +0200 |
parents | a2459defd4b6 |
children | 7c1f20407b3e |
files | pytouhou/formats/anm0.py pytouhou/game/background.py pytouhou/game/eclrunner.py pytouhou/game/enemymanager.py pytouhou/game/sprite.py pytouhou/vm/__init__.py pytouhou/vm/anmrunner.py pytouhou/vm/common.py pytouhou/vm/eclrunner.py |
diffstat | 8 files changed, 275 insertions(+), 165 deletions(-) [+] |
line wrap: on
line diff
--- a/pytouhou/formats/anm0.py +++ b/pytouhou/formats/anm0.py @@ -13,12 +13,34 @@ ## from struct import pack, unpack -from pytouhou.utils.helpers import read_string +from pytouhou.utils.helpers import read_string, get_logger + + +logger = get_logger(__name__) #TODO: refactor/clean up class Animations(object): + _instructions = {0: ('', 'delete'), + 1: ('I', 'set_sprite'), + 2: ('ff', 'set_scale'), + 3: ('I', 'set_alpha'), + 4: ('BBBx', 'set_color'), + 5: ('I', 'jump'), + 7: ('', 'toggle_mirrored'), + 9: ('fff', 'set_3d_rotations'), + 10: ('fff', 'set_3d_rotations_speed'), + 11: ('ff', 'set_scale_speed'), + 12: ('ii', 'fade'), + 15: ('', 'keep_still'), + 16: ('i', 'set_random_sprite'), + 23: ('', 'set_corner_relative_placement'), + 27: ('f', 'shift_texture_x'), + 28: ('f', 'shift_texture_y'), + 30: ('ffi', 'scale_in')} + + def __init__(self): self.size = (0, 0) self.first_name = None @@ -71,40 +93,24 @@ class Animations(object): while True: #TODO instruction_offsets.append(file.tell() - offset) - time, instr_type, length = unpack('<HBB', file.read(4)) + time, opcode, length = unpack('<HBB', file.read(4)) data = file.read(length) - if instr_type == 1: # set_sprite - args = unpack('<I', data) - elif instr_type == 2: # set_scale - args = unpack('<ff', data) - elif instr_type == 3: # set_alpha - args = unpack('<I', data) - elif instr_type == 4: # set_color - args = unpack('<BBBx', data) - elif instr_type == 5: # jump - # Translate offset to instruction index - args = (instruction_offsets.index(unpack('<I', data)[0]),) - elif instr_type == 9: # set_3d_rotation - args = unpack('<fff', data) - elif instr_type == 10: # set_3d_rotation_speed - args = unpack('<fff', data) - elif instr_type == 11: # set_scale_speed - args = unpack('<ff', data) - elif instr_type == 12: # fade - args = unpack('<ii', data) - elif instr_type == 16: # set_random_sprite - args = unpack('<ii', data) - elif instr_type == 27: # shift_texture_x - args = unpack('<f', data) - elif instr_type == 28: # shift_texture_y - args = unpack('<f', data) - elif instr_type == 30: # scale_in - args = unpack('<ffi', data) + if opcode in cls._instructions: + args = unpack('<%s' % cls._instructions[opcode][0], data) else: args = (data,) - anm.scripts[i].append((time, instr_type, args)) - if instr_type == 0: + logger.warn('unknown opcode %d', opcode) + + anm.scripts[i].append((time, opcode, args)) + if opcode == 0: break + + # Translate offsets to instruction pointers + for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(anm.scripts[i])): + time, opcode, args = instr + if opcode == 5: + args = (instruction_offsets.index(args[0]),) + anm.scripts[i][j] = time, opcode, args #TODO return anm
--- a/pytouhou/game/background.py +++ b/pytouhou/game/background.py @@ -19,6 +19,7 @@ import struct from itertools import chain from pytouhou.utils.interpolator import Interpolator +from pytouhou.vm.anmrunner import ANMRunner from pytouhou.game.sprite import Sprite @@ -72,8 +73,11 @@ class Background(object): faces = [] for script_index, ox, oy, oz, width_override, height_override in obj.quads: #TODO: per-texture rendering - anm, sprite = self.anm_wrapper.get_sprite(script_index) - if sprite.update(): + sprite = Sprite() + anm_runner = ANMRunner(self.anm_wrapper, script_index, sprite) + anm_runner.run_frame() + sprite.update() + if sprite._changed: sprite.update_vertices_uvs_colors(width_override, height_override) uvs, vertices = sprite._uvs, tuple((x + ox, y + oy, z + oz) for x, y, z in sprite._vertices) colors = sprite._colors
--- a/pytouhou/game/enemymanager.py +++ b/pytouhou/game/enemymanager.py @@ -18,7 +18,8 @@ from io import BytesIO import os from struct import unpack, pack from pytouhou.utils.interpolator import Interpolator -from pytouhou.game.eclrunner import ECLRunner +from pytouhou.vm.eclrunner import ECLRunner +from pytouhou.vm.anmrunner import ANMRunner from pytouhou.game.sprite import Sprite from math import cos, sin, atan2 @@ -26,8 +27,8 @@ from math import cos, sin, atan2 class Enemy(object): def __init__(self, pos, life, _type, anm_wrapper): self._anm_wrapper = anm_wrapper - self._anm = None self._sprite = None + self._anmrunner = None self._removed = False self._type = _type self._was_visible = False @@ -91,7 +92,8 @@ class Enemy(object): def set_anim(self, index): - self._anm, self._sprite = self._anm_wrapper.get_sprite(index) + self._sprite = Sprite() + self._anmrunner = ANMRunner(self._anm_wrapper, index, self._sprite) def set_pos(self, x, y, z): @@ -139,7 +141,7 @@ class Enemy(object): def get_objects_by_texture(self): objects_by_texture = {} - key = self._anm.first_name, self._anm.secondary_name + key = self._sprite.anm.first_name, self._sprite.anm.secondary_name if not key in objects_by_texture: objects_by_texture[key] = (0, [], [], []) vertices = tuple((x + self.x, y + self.y, z) for x, y, z in self._sprite._vertices) @@ -150,7 +152,7 @@ class Enemy(object): return objects_by_texture - def update(self, frame): + def update(self): x, y = self.x, self.y if self.interpolator: self.interpolator.update(self.frame) @@ -195,20 +197,20 @@ class Enemy(object): self.x, self.y = x, y + + #TODO + if self._anmrunner and not self._anmrunner.run_frame(): + self._anmrunner = None + if self._sprite: - changed = self._sprite.update() - visible = self.is_visible(384, 448) - if changed and visible: - self._sprite.update_vertices_uvs_colors() - elif not self._sprite.playing: - visible = False + if self._sprite._removed: self._sprite = None - else: - visible = False - + else: + self._sprite.update() + if self._sprite._changed: + self._sprite.update_vertices_uvs_colors() self.frame += 1 - return visible @@ -255,7 +257,11 @@ class EnemyManager(object): self.enemies[:] = (enemy for enemy in self.enemies if not enemy._removed) # Update enemies - visible_enemies = [enemy for enemy in self.enemies if enemy.update(frame)] + for enemy in self.enemies: + enemy.update() + + # Filter out non-visible enemies + visible_enemies = [enemy for enemy in self.enemies if enemy.is_visible(384, 448)] #TODO for enemy in visible_enemies: enemy._was_visible = True @@ -265,12 +271,10 @@ class EnemyManager(object): enemy._removed = True self.enemies.remove(enemy) - #TODO: disable boss mode if it is dead/it has timeout if self._game_state.boss and self._game_state.boss._removed: self._game_state.boss = None - # Add enemies to vertices/uvs self.objects_by_texture = {} for enemy in visible_enemies:
--- a/pytouhou/game/sprite.py +++ b/pytouhou/game/sprite.py @@ -13,30 +13,35 @@ ## -from random import randrange -from struct import unpack - from pytouhou.utils.matrix import Matrix -from pytouhou.utils.helpers import get_logger - -logger = get_logger(__name__) class AnmWrapper(object): def __init__(self, anm_files): self.anm_files = list(anm_files) - def get_sprite(self, script_index): + + def get_sprite(self, sprite_index): + for anm in self.anm_files: + if sprite_index in anm.sprites: + return anm, anm.sprites[sprite_index] + raise IndexError + + + def get_script(self, script_index): for anm in self.anm_files: if script_index in anm.scripts: - return anm, Sprite(anm, script_index) + return anm, anm.scripts[script_index] + raise IndexError class Sprite(object): - def __init__(self, anm, script_index): - self.anm = anm - self.script_index = script_index + def __init__(self): + self.anm = None + self._removed = False + self._changed = False + self.texcoords = (0, 0, 0, 0) # x, y, width, height self.texoffsets = (0., 0.) self.mirrored = False @@ -45,9 +50,6 @@ class Sprite(object): self.rotations_3d = (0., 0., 0.) self.rotations_speed_3d = (0., 0., 0.) self.corner_relative_placement = False - self.instruction_pointer = 0 - self.keep_still = False - self.playing = True self.frame = 0 self.color = (255, 255, 255) self.alpha = 255 @@ -95,78 +97,11 @@ class Sprite(object): self._uvs, self._vertices = uvs, zip(d[0], d[1], d[2]) - def update(self): - if not self.playing: - return False - - changed = False - if not self.keep_still: - script = self.anm.scripts[self.script_index] - frame = self.frame - while True: - try: - frame, instr_type, args = script[self.instruction_pointer] - except IndexError: - self.playing = False - return False + if self.rotations_speed_3d != (0., 0., 0.) or self.scale_speed != (0., 0.): + ax, ay, az = self.rotations_3d + sax, say, saz = self.rotations_speed_3d + self.rotations_3d = ax + sax, ay + say, az + saz + self.rescale = self.rescale[0] + self.scale_speed[0], self.rescale[1] + self.scale_speed[1] + self._changed = True - if frame > self.frame: - break - else: - self.instruction_pointer += 1 - if frame == self.frame: - changed = True - if instr_type == 0: - self.playing = False - return False - if instr_type == 1: - #TODO: handle out-of-anm sprites - self.texcoords = self.anm.sprites[args[0]] - elif instr_type == 2: - self.rescale = args - elif instr_type == 3: - self.alpha = args[0] % 256 #TODO - elif instr_type == 4: - b, g, r = args - self.color = (r, g, b) - elif instr_type == 5: - self.instruction_pointer, = args - self.frame = script[self.instruction_pointer][0] - elif instr_type == 7: - self.mirrored = not self.mirrored - elif instr_type == 9: - self.rotations_3d = args - elif instr_type == 10: - self.rotations_speed_3d = args - elif instr_type == 11: - self.scale_speed = args - elif instr_type == 16: - #TODO: handle out-of-anm sprites - #TODO: use the game's PRNG? - self.texcoords = self.anm.sprites[args[0] + randrange(args[1])] - elif instr_type == 23: - self.corner_relative_placement = True #TODO - elif instr_type == 27: - tox, toy = self.texoffsets - self.texoffsets = tox + args[0], toy - elif instr_type == 28: - tox, toy = self.texoffsets - self.texoffsets = tox, toy + args[0] - elif instr_type in (15, 21): - self.keep_still = True - break - else: - logger.warn('unhandled instruction %d (args: %r)', instr_type, args) - self.frame += 1 - - ax, ay, az = self.rotations_3d - sax, say, saz = self.rotations_speed_3d - self.rotations_3d = ax + sax, ay + say, az + saz - self.rescale = self.rescale[0] + self.scale_speed[0], self.rescale[1] + self.scale_speed[1] - - if self.rotations_speed_3d != (0., 0., 0.) or self.scale_speed != (0., 0.): - return True - - return changed -
new file mode 100644 --- /dev/null +++ b/pytouhou/vm/anmrunner.py @@ -0,0 +1,148 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + + +from random import randrange + +from pytouhou.utils.helpers import get_logger +from pytouhou.vm.common import MetaRegistry, instruction + +logger = get_logger(__name__) + + +class ANMRunner(object): + __metaclass__ = MetaRegistry + __slots__ = ('_anm_wrapper', '_sprite', '_running', + 'script', 'instruction_pointer', 'frame') + + + def __init__(self, anm_wrapper, script_id, sprite): + self._anm_wrapper = anm_wrapper + self._sprite = sprite + self._running = True + + anm, self.script = anm_wrapper.get_script(script_id) + self.frame = 0 + self.instruction_pointer = 0 + pass + + + def run_frame(self): + if self._sprite._removed: + return False + + while self._running: + try: + frame, instr_type, args = self.script[self.instruction_pointer] + except IndexError: + return False + + if frame > self.frame: + break + else: + self.instruction_pointer += 1 + + if frame == self.frame: + try: + callback = self._handlers[instr_type] + except KeyError: + logger.warn('unhandled opcode %d (args: %r)', instr_type, args) + else: + callback(self, *args) + self._sprite._changed = True + self.frame += 1 + return self._running + + + @instruction(0) + def remove(self): + self._sprite._removed = True + self._running = True + + + @instruction(1) + def load_sprite(self, sprite_index): + self._sprite.anm, self._sprite.texcoords = self._anm_wrapper.get_sprite(sprite_index) + + + @instruction(2) + def set_scale(self, sx, sy): + self._sprite.rescale = sx, sy + + + @instruction(3) + def set_alpha(self, alpha): + self._sprite.alpha = alpha % 256 #TODO + + + @instruction(4) + def set_color(self, b, g, r): + self._sprite.color = (r, g, b) + + + @instruction(5) + def jump(self, instruction_pointer): + #TODO: is that really how it works? + self.instruction_pointer = instruction_pointer + self.frame = self.script[self.instruction_pointer][0] + + + @instruction(7) + def toggle_mirrored(self): + self._sprite.mirrored = not self._sprite.mirrored + + + @instruction(9) + def set_rotations_3d(self, rx, ry, rz): + self._sprite.rotations_3d = rx, ry, rz + + + @instruction(10) + def set_rotations_speed_3d(self, srx, sry, srz): + self._sprite.rotations_speed_3d = srx, sry, srz + + + @instruction(11) + def set_scale_speed(self, ssx, ssy): + self._sprite.scale_speed = ssx, ssy + + + @instruction(16) + def load_random_sprite(self, min_idx, amp): + #TODO: use the game's PRNG? + self.load_sprite(min_idx + randrange(amp)) + + + @instruction(23) + def set_corner_relative_placement(self): + self._sprite.corner_relative_placement = True #TODO + + + @instruction(27) + def shift_texture_x(self, dx): + tox, toy = self._sprite.texoffsets + self._sprite.texoffsets = tox + dx, toy + + + @instruction(28) + def shift_texture_y(self, dy): + tox, toy = self._sprite.texoffsets + self._sprite.texoffsets = tox, toy + dy + + + @instruction(15) + @instruction(21) #TODO + def keep_still(self): + self._running = False +
new file mode 100644 --- /dev/null +++ b/pytouhou/vm/common.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + + +class MetaRegistry(type): + def __new__(mcs, name, bases, classdict): + instruction_handlers = {} + for item in classdict.itervalues(): + try: + instruction_ids = item._instruction_ids + except AttributeError: + pass + else: + for id_ in instruction_ids: + instruction_handlers[id_] = item + classdict['_handlers'] = instruction_handlers + return type.__new__(mcs, name, bases, classdict) + + + +def instruction(instruction_id): + def _decorator(func): + if not hasattr(func, '_instruction_ids'): + func._instruction_ids = set() + func._instruction_ids.add(instruction_id) + return func + return _decorator
rename from pytouhou/game/eclrunner.py rename to pytouhou/vm/eclrunner.py --- a/pytouhou/game/eclrunner.py +++ b/pytouhou/vm/eclrunner.py @@ -17,33 +17,9 @@ from math import atan2, cos, sin from pytouhou.utils.helpers import get_logger -logger = get_logger(__name__) - - +from pytouhou.vm.common import MetaRegistry, instruction -class MetaRegistry(type): - def __new__(mcs, name, bases, classdict): - instruction_handlers = {} - for item in classdict.itervalues(): - try: - instruction_ids = item._instruction_ids - except AttributeError: - pass - else: - for id_ in instruction_ids: - instruction_handlers[id_] = item - classdict['_handlers'] = instruction_handlers - return type.__new__(mcs, name, bases, classdict) - - - -def instruction(instruction_id): - def _decorator(func): - if not hasattr(func, '_instruction_ids'): - func._instruction_ids = set() - func._instruction_ids.add(instruction_id) - return func - return _decorator +logger = get_logger(__name__) @@ -79,7 +55,6 @@ class ECLRunner(object): #TODO # Now, process script - frame = self.frame while True: try: frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer]