changeset 497:3da7395f39e3

Make enemy callbacks programmables.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 14 Oct 2013 12:20:55 +0200
parents 104c737ce8b3
children c9c2fb873dbd
files pytouhou/game/enemy.pxd pytouhou/game/enemy.pyx pytouhou/game/game.pyx pytouhou/vm/eclrunner.py
diffstat 4 files changed, 55 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/game/enemy.pxd
+++ b/pytouhou/game/enemy.pxd
@@ -3,11 +3,20 @@ from pytouhou.game.game cimport Game
 from pytouhou.game.player cimport Player
 from pytouhou.utils.interpolator cimport Interpolator
 
+cdef class Callback:
+    cdef function
+    cdef public tuple args  # XXX: public only for ECL’s copy_callbacks.
+
+    cpdef enable(self, function, tuple args)
+    cpdef disable(self)
+    cpdef fire(self)
+
 cdef class Enemy(Element):
     cdef public double z, angle, speed, rotation_speed, acceleration
-    cdef public long _type, bonus_dropped, die_score, frame, life, death_flags, current_laser_id, death_callback, boss_callback, low_life_callback, low_life_trigger, timeout, timeout_callback, remaining_lives, bullet_launch_interval, bullet_launch_timer, death_anim, direction, update_mode
+    cdef public long _type, bonus_dropped, die_score, frame, life, death_flags, current_laser_id, low_life_trigger, timeout, remaining_lives, bullet_launch_interval, bullet_launch_timer, death_anim, direction, update_mode
     cdef public bint visible, was_visible, touchable, collidable, damageable, boss, automatic_orientation, delay_attack
     cdef public tuple difficulty_coeffs, extended_bullet_attributes, bullet_attributes, bullet_launch_offset, movement_dependant_sprites, screen_box
+    cdef public Callback death_callback, boss_callback, low_life_callback, timeout_callback
     cdef public dict laser_by_id
     cdef public list aux_anm
     cdef public Interpolator interpolator, speed_interpolator
--- a/pytouhou/game/enemy.pyx
+++ b/pytouhou/game/enemy.pyx
@@ -21,6 +21,27 @@ from pytouhou.game.laser cimport Laser, 
 from pytouhou.game.effect cimport Effect
 
 
+cdef class Callback:
+    def __init__(self, function=None, args=()):
+        self.function = function
+        self.args = args
+
+    def __nonzero__(self):
+        return self.function is not None
+
+    cpdef enable(self, function, tuple args):
+        self.function = function
+        self.args = args
+
+    cpdef disable(self):
+        self.function = None
+
+    cpdef fire(self):
+        if self.function is not None:
+            self.function(*self.args)
+            self.function = None
+
+
 cdef class Enemy(Element):
     def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, Game game):
         Element.__init__(self)
@@ -50,14 +71,15 @@ cdef class Enemy(Element):
         self.laser_by_id = {}
         self.bullet_attributes = None
         self.bullet_launch_offset = (0, 0)
-        self.death_callback = -1
-        self.boss_callback = -1
-        self.low_life_callback = -1
         self.low_life_trigger = -1
         self.timeout = -1
-        self.timeout_callback = -1
         self.remaining_lives = 0
 
+        self.death_callback = Callback()
+        self.boss_callback = Callback()
+        self.low_life_callback = Callback()
+        self.timeout_callback = Callback()
+
         self.automatic_orientation = False
 
         self.bullet_launch_interval = 0
@@ -382,7 +404,7 @@ cdef class Enemy(Element):
         #TODO: implement missing callbacks and clean up!
         if self.life <= 0 and self.touchable:
             self.timeout = -1 #TODO: not really true, the timeout is frozen
-            self.timeout_callback = -1
+            self.timeout_callback.disable()
             death_flags = self.death_flags & 7
 
             self.die_anim()
@@ -428,14 +450,12 @@ cdef class Enemy(Element):
                     self.life = 1
                     self.death_flags = 0
 
-            if death_flags != 0 and self.death_callback > -1:
-                self.process.switch_to_sub(self.death_callback)
-                self.death_callback = -1
-        elif self.life <= self.low_life_trigger and self.low_life_callback > -1:
-            self.process.switch_to_sub(self.low_life_callback)
-            self.low_life_callback = -1
+            if death_flags != 0:
+                self.death_callback.fire()
+        elif self.life <= self.low_life_trigger and self.low_life_callback:
+            self.low_life_callback.fire()
             self.low_life_trigger = -1
-            self.timeout_callback = -1
+            self.timeout_callback.disable()
         elif self.timeout != -1 and self.frame == self.timeout:
             self.frame = 0
             self.timeout = -1
@@ -446,12 +466,11 @@ cdef class Enemy(Element):
                 self.life = self.low_life_trigger
                 self.low_life_trigger = -1
 
-            if self.timeout_callback > -1:
-                self.process.switch_to_sub(self.timeout_callback)
-                self.timeout_callback = -1
+            if self.timeout_callback:
+                self.timeout_callback.fire()
             #TODO: this is only done under certain (unknown) conditions!
             # but it shouldn't hurt anyway, as the only option left is to crash!
-            elif self.death_callback > -1:
+            elif self.death_callback:
                 self.life = 0 #TODO: do this next frame? Bypass self.touchable?
             else:
                 raise Exception('What the hell, man!')
--- a/pytouhou/game/game.pyx
+++ b/pytouhou/game/game.pyx
@@ -203,10 +203,9 @@ cdef class Game:
                 pass # Bosses are immune to 96
             elif enemy.touchable:
                 enemy.life = 0
-            elif enemy.death_callback > 0:
+            else:
                 #TODO: check
-                enemy.process.switch_to_sub(enemy.death_callback)
-                enemy.death_callback = -1
+                enemy.death_callback.fire()
 
 
     cpdef new_effect(self, pos, long anim, anm=None, long number=1):
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -103,13 +103,11 @@ class ECLMainRunner(object):
 
     @instruction(10)
     def resume_ecl(self, sub, instr_type, unk1, unk2):
-        boss = self._game.boss
+        boss = self._game.boss._enemy
         self._game.msg_wait = False
-        if boss._enemy.boss_callback > -1:
-            boss.switch_to_sub(boss._enemy.boss_callback)
-            boss._enemy.boss_callback = -1
-        else:
+        if not boss.boss_callback:
             raise Exception #TODO
+        boss.boss_callback.fire()
 
 
     @instruction(12)
@@ -843,13 +841,13 @@ class ECLRunner(object):
 
     @instruction(108)
     def set_death_callback(self, sub):
-        self._enemy.death_callback = sub
+        self._enemy.death_callback.enable(self.switch_to_sub, (sub,))
 
 
     @instruction(109)
     def memory_write(self, value, index):
         if index == 0:
-            self._enemy.boss_callback = value
+            self._enemy.boss_callback.enable(self.switch_to_sub, (value,))
         else:
             raise Exception #TODO
 
@@ -880,7 +878,7 @@ class ECLRunner(object):
 
     @instruction(114)
     def set_low_life_callback(self, sub):
-        self._enemy.low_life_callback = sub
+        self._enemy.low_life_callback.enable(self.switch_to_sub, (sub,))
 
 
     @instruction(115)
@@ -891,7 +889,7 @@ class ECLRunner(object):
 
     @instruction(116)
     def set_timeout_callback(self, sub):
-        self._enemy.timeout_callback = sub
+        self._enemy.timeout_callback.enable(self.switch_to_sub, (sub,))
 
 
     @instruction(117)
@@ -1144,5 +1142,5 @@ class ECLRunner(object):
 
     @instruction(133)
     def copy_callbacks(self):
-        self._enemy.timeout_callback = self._enemy.death_callback
+        self._enemy.timeout_callback.enable(self.switch_to_sub, (self._enemy.death_callback.args[0],))