Mercurial > touhou
diff pytouhou/game/eclrunner.py @ 49:cbe1cb50f2fd
Refactor ECLRunner/EnemyManager so that all VM stuff goes to ECLRunner
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Mon, 22 Aug 2011 19:23:00 +0200 |
parents | 8353c33d53d4 |
children | 811cefefb5c8 |
line wrap: on
line diff
--- a/pytouhou/game/eclrunner.py +++ b/pytouhou/game/eclrunner.py @@ -1,59 +1,128 @@ +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 + + + class ECLRunner(object): - def __init__(self, ecl, sub, frame=0, instruction_pointer=0, implementation=None): - self.ecl = ecl + __metaclass__ = MetaRegistry + __slots__ = ('_ecl', '_enemy', '_game_state', 'variables', 'sub', 'frame', + 'instruction_pointer', 'stack') + def __init__(self, ecl, sub, enemy, game_state): + # Things not supposed to change + self._ecl = ecl + self._enemy = enemy + self._game_state = game_state + + # Things supposed to change (and be put in the stack) self.variables = [0, 0, 0, 0, 0., 0., 0., 0., 0, 0, 0, 0] self.sub = sub - self.frame = frame - self.instruction_pointer = instruction_pointer + self.frame = 0 + self.instruction_pointer = 0 self.stack = [] - self.implementation = {4: self.set_variable, - 5: self.set_variable, - 2: self.relative_jump, - 3: self.relative_jump_ex, - 20: self.add, - 21: self.substract, - 35: self.call, - 36: self.ret, - 109: self.memory_write} - if implementation: - self.implementation.update(implementation) + + def run_iteration(self): + # First, if enemy is dead, return + if self._enemy._removed: + return False + + # Then, check for callbacks + #TODO + + # Now, process script + frame = self.frame + try: + while frame <= self.frame: + frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] + + #TODO: skip bad ranks + + if frame == self.frame: + try: + callback = self._handlers[instr_type] + except KeyError: + print('Warning: unhandled opcode %d!' % instr_type) #TODO + else: + callback(self, *args) + frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] + if frame <= self.frame: + self.instruction_pointer += 1 + except IndexError: + return False + + self.frame += 1 + return True - def _get_value(self, value): #TODO: -10013 and beyond! - assert not -10025 <= value <= -10013 + def _getval(self, value): if -10012 <= value <= -10001: return self.variables[int(-10001-value)] + elif -10025 <= value <= -10013: + raise NotImplementedError #TODO else: return value - def add(self, variable_id, a, b): - #TODO: proper variable handling - #TODO: int vs float thing - self.variables[-10001-variable_id] = self._get_value(a) + self._get_value(b) + def _setval(self, variable_id, value): + if -10012 <= value <= -10001: + self.variables[int(-10001-variable_id)] = value + elif -10025 <= value <= -10013: + raise NotImplementedError #TODO + else: + raise IndexError #TODO + + + @instruction(1) + def destroy(self, arg): + #TODO: arg? + self._enemy._removed = True - def substract(self, variable_id, a, b): - #TODO: proper variable handling - #TODO: int vs float thing - self.variables[-10001-variable_id] = self._get_value(a) - self._get_value(b) - + @instruction(2) + def relative_jump(self, frame, instruction_pointer): + self.frame, self.instruction_pointer = frame, instruction_pointer - def memory_write(self, value, index): - #TODO - #XXX: this is a hack to display bosses although we don't handle MSG :) - if index == 0: - self.sub = value - self.frame = 0 - self.instruction_pointer = 0 + @instruction(3) + def relative_jump_ex(self, frame, instruction_pointer, variable_id): + if self.variables[-10001-variable_id]: + self.variables[-10001-variable_id] -= 1 + self.frame, self.instruction_pointer = frame, instruction_pointer + @instruction(4) + @instruction(5) + def set_variable(self, variable_id, value): + #TODO: -10013 and beyond! + self.variables[-10001-variable_id] = self._getval(value) + + + @instruction(35) def call(self, sub, param1, param2): self.stack.append((self.sub, self.frame, self.instruction_pointer, self.variables)) @@ -65,43 +134,139 @@ class ECLRunner(object): 0, 0, 0, 0] + @instruction(36) def ret(self): self.sub, self.frame, self.instruction_pointer, self.variables = self.stack.pop() - def set_variable(self, variable_id, value): - #TODO: -10013 and beyond! - self.variables[-10001-variable_id] = self._get_value(value) + @instruction(20) + def add(self, variable_id, a, b): + #TODO: proper variable handling + #TODO: int vs float thing + self.variables[-10001-variable_id] = self._getval(a) + self._getval(b) + + + @instruction(21) + def substract(self, variable_id, a, b): + #TODO: proper variable handling + #TODO: int vs float thing + self.variables[-10001-variable_id] = self._getval(a) - self._getval(b) + + + @instruction(43) + def set_pos(self, x, y, z): + self._enemy.set_pos(x, y, z) + + + @instruction(45) + def set_angle_speed(self, angle, speed): + self._enemy.angle, self._enemy.speed = angle, speed + + + @instruction(46) + def set_rotation_speed(self, speed): + self._enemy.rotation_speed = speed - def relative_jump(self, frame, instruction_pointer): - self.frame, self.instruction_pointer = frame, instruction_pointer + @instruction(47) + def set_speed(self, speed): + self._enemy.speed = speed + + + @instruction(48) + def set_acceleration(self, acceleration): + self._enemy.acceleration = acceleration + + + @instruction(51) + def target_player(self, unknown, speed): + self._enemy.speed = speed #TODO: unknown + player_x, player_y = 192., 384.#TODO + self._enemy.angle = atan2(player_y - self._enemy.y, player_x - self._enemy.x) #TODO - def relative_jump_ex(self, frame, instruction_pointer, variable_id): - if self.variables[-10001-variable_id]: - self.variables[-10001-variable_id] -= 1 - self.frame, self.instruction_pointer = frame, instruction_pointer + @instruction(57) + def move_to(self, duration, x, y, z): + self._enemy.move_to(duration, x, y, z) + + + @instruction(77) + def set_bullet_interval(self, value): + self._enemy.bullet_launch_interval = value + + + @instruction(78) + def set_delay_attack(self): + self._enemy.delay_attack = True + + + @instruction(79) + def set_no_delay_attack(self): + self._enemy.delay_attack = False - def update(self): - frame = self.frame - try: - while frame <= self.frame: - frame, instr_type, rank_mask, param_mask, args = self.ecl.subs[self.sub][self.instruction_pointer] + @instruction(81) + def set_bullet_launch_offset(self, x, y, z): + self._enemy.bullet_launch_offset = (x, y) + + + @instruction(97) + def set_anim(self, sprite_index): + self._enemy.set_anim(sprite_index) + + + @instruction(98) + def set_multiple_anims(self, default, end_left, end_right, left, right): + self._enemy.movement_dependant_sprites = end_left, end_right, left, right + self._enemy.set_anim(default) + + + @instruction(100) + def set_death_anim(self, sprite_index): + self._enemy.death_anim = sprite_index % 256 #TODO + + + @instruction(103) + def set_hitbox(self, width, height, depth): + self._enemy.hitbox = (width, height) + + + @instruction(105) + def set_vulnerable(self, vulnerable): + self._enemy.vulnerable = bool(vulnerable & 1) + - if frame == self.frame: - try: - callback = self.implementation[instr_type] - except KeyError: - print('Warning: unhandled opcode %d!' % instr_type) #TODO - else: - callback(*args) - frame, instr_type, rank_mask, param_mask, args = self.ecl.subs[self.sub][self.instruction_pointer] - if frame <= self.frame: - self.instruction_pointer += 1 - except IndexError: - pass #TODO: script ended, destroy enemy + @instruction(108) + def set_death_callback(self, sub): + self._enemy.death_callback = sub + + + @instruction(109) + def memory_write(self, value, index): + #TODO + #XXX: this is a hack to display bosses although we don't handle MSG :) + if index == 0: + self.sub = value + self.frame = 0 + self.instruction_pointer = 0 + - self.frame += 1 + @instruction(113) + def set_low_life_trigger(self, value): + self._enemy.low_life_trigger = value + + + @instruction(114) + def set_low_life_callback(self, sub): + self._enemy.low_life_callback = sub + + @instruction(115) + def set_timeout(self, timeout): + self._enemy.timeout = timeout + + + @instruction(126) + def set_remaining_lives(self, lives): + self._enemy.remaining_lives = lives +