changeset 193:9f58e2a6e950

Fix particles, fix "random" item popping, change update order to match the original game's more closely.
author Thibaut Girka <thib@sitedethib.com>
date Fri, 28 Oct 2011 12:38:26 +0200
parents 5e84dfd153ab
children efa847ee8b3c
files pytouhou/formats/t6rp.py pytouhou/game/effect.py pytouhou/game/enemy.py pytouhou/game/game.py pytouhou/game/player.py pytouhou/vm/eclrunner.py
diffstat 6 files changed, 91 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/formats/t6rp.py
+++ b/pytouhou/formats/t6rp.py
@@ -71,8 +71,8 @@ class T6RP(object):
                 raise Exception #TODO
 
         replay.unknown3 = unpack('<B', file.read(1))
-        replay.date = read_string(file, 9, 'ascii')
-        replay.name = read_string(file, 9, 'ascii').rstrip()
+        replay.date = file.read(9) #read_string(file, 9, 'ascii')
+        replay.name = file.read(9) #read_string(file, 9, 'ascii').rstrip()
         replay.unknown4, replay.score, replay.unknown5, replay.slowdown, replay.unknown6 = unpack('<HIIfI', file.read(18))
 
         stages_offsets = unpack('<7I', file.read(28))
--- a/pytouhou/game/effect.py
+++ b/pytouhou/game/effect.py
@@ -40,7 +40,7 @@ class Effect(object):
 
 
 class Particle(object):
-    def __init__(self, start_pos, index, anm_wrapper, size, amp, delay, game):
+    def __init__(self, start_pos, index, anm_wrapper, size, amp, game):
         self._sprite = Sprite()
         self._sprite.anm, self._sprite.texcoords = anm_wrapper.get_sprite(index)
         self._game = game
@@ -56,7 +56,6 @@ class Particle(object):
         self.scale_interpolator = None
         self.rotations_interpolator = None
 
-        self.delay = delay
         self.amp = amp
 
 
@@ -73,7 +72,7 @@ class Particle(object):
 
 
     def update(self):
-        if (self.frame == 0 and not self.delay) or (self.frame == 1 and self.delay):
+        if self.frame == 0:
             self.set_end_pos(self.amp)
 
         if self.pos_interpolator:
--- a/pytouhou/game/enemy.py
+++ b/pytouhou/game/enemy.py
@@ -187,7 +187,7 @@ class Enemy(object):
             if self._game.stage in [1, 2, 7]:
                 color = 3
         for i in range(number):
-            self._game.new_particle((self.x, self.y), color, 4., 256, delay=False) #TODO: find the real size.
+            self._game.new_particle((self.x, self.y), color, 4., 256) #TODO: find the real size.
 
 
     def set_pos(self, x, y, z):
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -48,8 +48,6 @@ class Game(object):
         self.difficulty = difficulty
         self.boss = None
         self.spellcard = None
-        self.deaths_count = 0
-        self.next_bonus = 0
         self.bonus_list = [0,0,1,0,1,0,0,1,1,1,0,0,0,1,1,0,1,0,1,0,1,0,1,0,1,0,0,1,1,1,0,2]
         self.prng = prng or Random()
         self.frame = 0
@@ -60,10 +58,9 @@ class Game(object):
         ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
         self.ecl_runner = ECLMainRunner(ecl, self)
 
-        #TODO: The game calls it two times. What for?
         # See 102h.exe@0x413220 if you think you're brave enough.
-        self.prng.rand_uint16()
-        self.prng.rand_uint16()
+        self.deaths_count = self.prng.rand_uint16() % 3
+        self.next_bonus = self.prng.rand_uint16() % 8
 
 
     def drop_bonus(self, x, y, _type, end_pos=None):
@@ -87,8 +84,8 @@ class Game(object):
         self.effects.append(Effect(pos, anim, self.etama4))
 
 
-    def new_particle(self, pos, color, size, amp, delay=False):
-        self.effects.append(Particle(pos, 7 + 4 * color + self.prng.rand_uint16() % 4, self.etama4, size, amp, delay, self))
+    def new_particle(self, pos, color, size, amp):
+        self.effects.append(Particle(pos, 7 + 4 * color + self.prng.rand_uint16() % 4, self.etama4, size, amp, self))
 
 
     def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score):
@@ -108,8 +105,49 @@ class Game(object):
         self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if not bullet._removed]
         self.items = [item for item in self.items if not item._removed]
 
+
         # 3. Let's play!
-        #TODO: check update orders
+        # In the original game, updates are done in prioritized functions called "chains"
+        # We have to mimic this functionnality to be replay-compatible with the official game.
+
+        # Pri 6 is background
+        self.update_players(keystate) # Pri 7
+        self.update_enemies() # Pri 9
+        self.update_effects() # Pri 10
+        self.update_bullets() # Pri 11
+        # Pri 12 is HUD
+
+        # 4. Cleaning
+        self.cleanup()
+
+        self.frame += 1
+
+
+    def update_enemies(self):
+        for enemy in self.enemies:
+            enemy.update()
+
+        # Check for collisions
+        for enemy in self.enemies:
+            ex, ey = enemy.x, enemy.y
+            ehalf_size_x, ehalf_size_y = enemy.hitbox_half_size
+            ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x
+            ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y
+
+            for bullet in self.players_bullets:
+                half_size = bullet.hitbox_half_size
+                bx, by = bullet.x, bullet.y
+                bx1, bx2 = bx - half_size, bx + half_size
+                by1, by2 = by - half_size, by + half_size
+
+                if not (bx2 < ex1 or bx1 > ex2
+                        or by2 < ey1 or by1 > ey2):
+                    bullet.collide()
+                    enemy.on_attack(bullet)
+                    player.state.score += 90 # found experimentally
+
+
+    def update_players(self, keystate):
         for player in self.players:
             player.update(keystate) #TODO: differentiate keystates (multiplayer mode)
             if player.state.x < 8.:
@@ -121,26 +159,53 @@ class Game(object):
             if player.state.y > 448.-16: #TODO
                 player.state.y = 448.-16
 
-        for enemy in self.enemies:
-            enemy.update()
-
-        for enemy in self.effects:
-            enemy.update()
-
-        for bullet in self.bullets:
+        for bullet in self.players_bullets:
             bullet.update()
 
+        # Check for collisions
+        for player in self.players:
+            if not player.state.touchable:
+                continue
+
+            px, py = player.x, player.y
+            phalf_size = player.hitbox_half_size
+            px1, px2 = px - phalf_size, px + phalf_size
+            py1, py2 = py - phalf_size, py + phalf_size
+
+            ghalf_size = player.graze_hitbox_half_size
+            gx1, gx2 = px - ghalf_size, px + ghalf_size
+            gy1, gy2 = py - ghalf_size, py + ghalf_size
+
+            #TODO: Should that be done here or in update_enemies?
+            for enemy in self.enemies:
+                half_size_x, half_size_y = enemy.hitbox_half_size
+                bx, by = enemy.x, enemy.y
+                bx1, bx2 = bx - half_size_x, bx + half_size_x
+                by1, by2 = by - half_size_y, by + half_size_y
+
+                #TODO: box-box or point-in-box?
+                if enemy.touchable and not (bx2 < px1 or bx1 > px2
+                                            or by2 < py1 or by1 > py2):
+                    enemy.on_collide()
+                    if player.state.invulnerable_time == 0:
+                        player.collide()
+
+
+    def update_effects(self):
+        for effect in self.effects:
+            effect.update()
+
+
+    def update_bullets(self):
         for bullet in self.cancelled_bullets:
             bullet.update()
 
-        for bullet in self.players_bullets:
+        for bullet in self.bullets:
             bullet.update()
 
         for item in self.items:
             item.update()
 
-        # 4. Check for collisions!
-        #TODO
         for player in self.players:
             if not player.state.touchable:
                 continue
@@ -171,22 +236,10 @@ class Game(object):
                     bullet.grazed = True
                     player.state.graze += 1
                     player.state.score += 500 # found experimentally
-                    self.new_particle((px, py), 0, .8, 192, delay=True) #TODO: find the real size and range.
+                    self.new_particle((px, py), 0, .8, 192) #TODO: find the real size and range.
                     #TODO: display a static particle during one frame at
                     # 12 pixels of the player, in the axis of the “collision”.
 
-            for enemy in self.enemies:
-                half_size_x, half_size_y = enemy.hitbox_half_size
-                bx, by = enemy.x, enemy.y
-                bx1, bx2 = bx - half_size_x, bx + half_size_x
-                by1, by2 = by - half_size_y, by + half_size_y
-
-                if enemy.touchable and not (bx2 < px1 or bx1 > px2
-                                            or by2 < py1 or by1 > py2):
-                    enemy.on_collide()
-                    if player.state.invulnerable_time == 0:
-                        player.collide()
-
             for item in self.items:
                 half_size = item.hitbox_half_size
                 bx, by = item.x, item.y
@@ -197,29 +250,6 @@ class Game(object):
                         or by2 < py1 or by1 > py2):
                     player.collect(item)
 
-        for enemy in self.enemies:
-            ex, ey = enemy.x, enemy.y
-            ehalf_size_x, ehalf_size_y = enemy.hitbox_half_size
-            ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x
-            ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y
-
-            for bullet in self.players_bullets:
-                half_size = bullet.hitbox_half_size
-                bx, by = bullet.x, bullet.y
-                bx1, bx2 = bx - half_size, bx + half_size
-                by1, by2 = by - half_size, by + half_size
-
-                if not (bx2 < ex1 or bx1 > ex2
-                        or by2 < ey1 or by1 > ey2):
-                    bullet.collide()
-                    enemy.on_attack(bullet)
-                    player.state.score += 90 # found experimentally
-
-        # 5. Cleaning
-        self.cleanup()
-
-        self.frame += 1
-
 
     def cleanup(self):
         # Filter out non-visible enemies
--- a/pytouhou/game/player.py
+++ b/pytouhou/game/player.py
@@ -79,7 +79,7 @@ class Player(object):
             self.death_time = self._game.frame
             self._game.new_death((self.state.x, self.state.y), 2)
             for i in range(16):
-                self._game.new_particle((self.state.x, self.state.y), 2, 4., 256, delay=True) #TODO: find the real size and range.
+                self._game.new_particle((self.state.x, self.state.y), 2, 4., 256) #TODO: find the real size and range.
 
 
     def collect(self, item):
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -132,7 +132,7 @@ class ECLRunner(object):
                     enm.drop_particles(7, 0)
                     self._game.drop_bonus(enm.x, enm.y, enm._bonus_dropped)
                 elif enm._bonus_dropped == -1:
-                    if self._game.deaths_count % 3:
+                    if self._game.deaths_count % 3 == 0:
                         enm.drop_particles(10, 0)
                         self._game.drop_bonus(enm.x, enm.y, self._game.bonus_list[self._game.next_bonus])
                         self._game.next_bonus = (self._game.next_bonus + 1) % 32