changeset 23:444ac7bca7bc

Refacto ECL stuff, add support for a few instructions, and add some culling
author Thibaut Girka <thib@sitedethib.com>
date Fri, 12 Aug 2011 19:13:43 +0200
parents fa87db09fc3a
children 93aa1b55d97c
files pytouhou/game/eclrunner.py pytouhou/game/enemymanager.py
diffstat 2 files changed, 158 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/pytouhou/game/eclrunner.py
@@ -0,0 +1,57 @@
+from struct import unpack
+
+
+class ECLRunner(object):
+    def __init__(self, ecl, sub, frame=0, instruction_pointer=0, implementation=None):
+        self.ecl = ecl
+
+        self.labels = {}
+        self.implementation = {4: ('HHI', self.set_label),
+                               3: ('IHHHH', self.goto)}
+        if implementation:
+            self.implementation.update(implementation)
+
+        self.sub = sub
+        self.frame = frame
+        self.instruction_pointer = instruction_pointer
+
+
+    def set_label(self, label, unknown, count):
+        assert unknown == 0xffff
+        self.labels[label] = (self.sub, self.instruction_pointer, count)
+
+
+    def goto(self, frame, unknown1, unknown2, label, unknown3):
+        try:
+            sub, instruction_pointer, count = self.labels[label]
+        except KeyError:
+            pass
+        else:
+            count -= 1
+            if count:
+                self.labels[label] = sub, instruction_pointer, count
+            else:
+                del self.labels[label]
+            self.frame = frame
+            self.sub, self.instruction_pointer = sub, instruction_pointer
+
+
+    def update(self):
+        frame = self.frame
+        while frame <= self.frame:
+            try:
+                frame, instr_type, rank_mask, param_mask, args = self.ecl.subs[self.sub][self.instruction_pointer]
+            except IndexError:
+                break #TODO: script ended, destroy enemy
+
+            if frame == self.frame:
+                try:
+                    format, callback = self.implementation[instr_type]
+                except KeyError:
+                    print('Warning: unhandled opcode %d!' % instr_type) #TODO
+                else:
+                    callback(*unpack('<' + format, args))
+                self.instruction_pointer += 1
+
+        self.frame += 1
+
--- a/pytouhou/game/enemymanager.py
+++ b/pytouhou/game/enemymanager.py
@@ -3,21 +3,23 @@ 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.game.sprite import Sprite
 from math import cos, sin, atan2
 
 
 class Enemy(object):
-    def __init__(self, pos, life, _type, script, anm_wrapper):
+    def __init__(self, pos, life, _type, ecl_runner, anm_wrapper):
         self.anm_wrapper = anm_wrapper
         self.anm = None
-        self.script = list(script)
+        self.ecl_runner = ecl_runner
         self.x, self.y = pos
         self.life = life
         self.type = _type
         self.frame = 0
         self.sprite = None
 
+        self.death_sprite = None
         self.movement_dependant_sprites = None
         self.direction = None
         self.interpolator = None #TODO
@@ -26,40 +28,93 @@ class Enemy(object):
         self.rotation_speed = 0.
         self.acceleration = 0.
 
+        self.hitbox = (0, 0)
+
+        self.ecl_runner.implementation.update({97: ('I', self.set_sprite),
+                                               98: ('HHHHHH', self.set_multiple_sprites),
+                                               45: ('ff', self.set_angle_speed),
+                                               43: ('fff', self.set_pos),
+                                               46: ('f', self.set_rotation_speed),
+                                               47: ('f', self.set_speed),
+                                               48: ('f', self.set_acceleration),
+                                               51: ('If', self.target_player),
+                                               57: ('Ifff', self.move_to),
+                                               100: ('I', self.set_death_sprite),
+                                               103: ('fff', self.set_hitbox)}) #TODO
+
+
+    def set_death_sprite(self, sprite_index):
+        self.death_sprite = sprite_index % 256 #TODO
+
+
+    def set_hitbox(self, width, height, depth):
+        self.hitbox = (width, height)
+
+
+    def set_sprite(self, sprite_index):
+        self.anm, self.sprite = self.anm_wrapper.get_sprite(sprite_index)
+
+
+    def set_multiple_sprites(self, default, end_left, end_right, left, right, unknown):
+        self.movement_dependant_sprites = end_left, end_right, left, right, unknown
+        self.anm, self.sprite = self.anm_wrapper.get_sprite(default)
+
+
+    def set_angle_speed(self, angle, speed):
+        self.angle, self.speed = angle, speed
+
+
+    def set_pos(self, x, y, z):
+        self.x, self.y = x, y
+        self.interpolator = Interpolator((x, y))
+        self.interpolator.set_interpolation_start(self.frame, (x, y))
+
+
+    def set_rotation_speed(self, speed):
+        self.rotation_speed = speed
+
+
+    def set_speed(self, speed):
+        self.speed = speed
+
+
+    def set_acceleration(self, acceleration):
+        self.acceleration = acceleration
+
+
+    def target_player(self, unknown, speed):
+        self.speed = speed #TODO: unknown
+        player_x, player_y = 192., 400.#TODO
+        self.angle = atan2(player_y - self.y, player_x - self.x)
+
+
+    def move_to(self, duration, x, y, z):
+        self.interpolator.set_interpolation_end(self.frame + duration, (x, y))
+
+
+    def is_visible(self, screen_width, screen_height):
+        if not self.sprite:
+            return False
+        if min(x for x, y, z in self.sprite._vertices) >= screen_width - self.x:
+            return False
+        if max(x for x, y, z in self.sprite._vertices) <= -self.x:
+            return False
+        if min(y for x, y, z in self.sprite._vertices) >= screen_height - self.y:
+            return False
+        if max(y for x, y, z in self.sprite._vertices) <= -self.y:
+            return False
+        return True
+
 
     def update(self, frame):
-        if not self.script:
-            return True
-        if self.script[0][0] == self.frame:
-            for instr_type, rank_mask, param_mask, args  in self.script.pop(0)[1]:
-                if instr_type == 1: # delete
-                    return False
-                elif instr_type == 97: # set_enemy_sprite
-                    script_index, = unpack('<I', args)
-                    self.anm, self.sprite = self.anm_wrapper.get_sprite(script_index)
-                elif instr_type == 98: #TODO
-                    default, end_left, end_right, left, right, unknown = unpack('<HHHHHH', args)
-                    self.movement_dependant_sprites = end_left, end_right, left, right, unknown
-                    self.anm, self.sprite = self.anm_wrapper.get_sprite(default)
-                elif instr_type == 43: # set_pos
-                    self.x, self.y, z = unpack('<fff', args)
-                    self.interpolator = Interpolator((self.x, self.y)) #TODO: better interpolation
-                    self.interpolator.set_interpolation_start(self.frame, (self.x, self.y))
-                elif instr_type == 45: # set_angle_speed
-                    self.angle, self.speed = unpack('<ff', args)
-                elif instr_type == 46: # set_angle
-                    self.rotation_speed, = unpack('<f', args)
-                elif instr_type == 47: # set_speed
-                    self.speed, = unpack('<f', args)
-                elif instr_type == 48: # set_acceleration
-                    self.acceleration, = unpack('<f', args)
-                elif instr_type == 51: # move_towards_player #TODO: main
-                    unknown, self.speed = unpack('<If', args) #TODO: unknown
-                    player_x, player_y = 192., 400.#TODO
-                    self.angle = atan2(player_y - self.y, player_x - self.x)
-                elif instr_type == 57:
-                    duration, x, y, z = unpack('<Ifff', args)
-                    self.interpolator.set_interpolation_end(self.frame + duration, (x, y))
+        #TODO
+        #if not self.script:
+        #    return True
+        #if self.script[0][0] == self.frame:
+        #    for instr_type, rank_mask, param_mask, args  in self.script.pop(0)[1]:
+        #        if instr_type == 1: # delete
+        #            return False
+        self.ecl_runner.update()
 
         x, y = self.x, self.y
         if self.interpolator:
@@ -103,7 +158,7 @@ class EnemyManager(object):
         self.stage = stage
         self.anm_wrapper = anm_wrapper
         self.main = []
-        self.subs = {}
+        self.ecl = ecl
         self.objects_by_texture = {}
         self.enemies = []
 
@@ -115,23 +170,21 @@ class EnemyManager(object):
                 self.main[-1][1].append((sub, instr_type, args))
 
 
-        # Populate subs
-        for i, sub in enumerate(ecl.subs):
-            for frame, instr_type, rank_mask, param_mask, args in sub:
-                if i not in self.subs:
-                    self.subs[i] = []
-                if not self.subs[i] or self.subs[i][-1][0] < frame:
-                    self.subs[i].append((frame, [(instr_type, rank_mask, param_mask, args)]))
-                elif self.subs[i][-1][0] == frame:
-                    self.subs[i][-1][1].append((instr_type, rank_mask, param_mask, args))
-
-
     def update(self, frame):
         if self.main and self.main[0][0] == frame:
             for sub, instr_type, args in self.main.pop(0)[1]:
                 if instr_type in (0, 2, 4, 6): # Normal/mirrored enemy
                     x, y, z, life, unknown1, unknown2, unknown3 = args
-                    self.enemies.append(Enemy((x, y), life, instr_type, self.subs[sub], self.anm_wrapper))
+                    ecl_runner = ECLRunner(self.ecl, sub)
+                    enemy = Enemy((x, y), life, instr_type, ecl_runner, self.anm_wrapper)
+
+                    def _enemy_deleter(unknown): #TOOD: unknown
+                        print('youhou!')
+                        self.enemies.remove(enemy)
+
+                    ecl_runner.implementation[1] = ('I', _enemy_deleter)
+
+                    self.enemies.append(enemy)
 
         # Update enemies
         for enemy in tuple(self.enemies):
@@ -143,7 +196,7 @@ class EnemyManager(object):
         self.objects_by_texture = {}
         for enemy in self.enemies:
             ox, oy = enemy.x, enemy.y
-            if enemy.sprite:
+            if enemy.is_visible(384, 448): #TODO
                 key = enemy.anm.first_name, enemy.anm.secondary_name
                 if not key in self.objects_by_texture:
                     self.objects_by_texture[key] = (0, [], [])