comparison pytouhou/game/game.py @ 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 1e501e3b6645
comparison
equal deleted inserted replaced
192:5e84dfd153ab 193:9f58e2a6e950
46 self.stage = stage 46 self.stage = stage
47 self.rank = rank 47 self.rank = rank
48 self.difficulty = difficulty 48 self.difficulty = difficulty
49 self.boss = None 49 self.boss = None
50 self.spellcard = None 50 self.spellcard = None
51 self.deaths_count = 0
52 self.next_bonus = 0
53 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] 51 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]
54 self.prng = prng or Random() 52 self.prng = prng or Random()
55 self.frame = 0 53 self.frame = 0
56 54
57 self.enm_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage, 55 self.enm_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage,
58 'stg%denm2.anm' % stage)) 56 'stg%denm2.anm' % stage))
59 self.etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) 57 self.etama4 = resource_loader.get_anm_wrapper(('etama4.anm',))
60 ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage) 58 ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
61 self.ecl_runner = ECLMainRunner(ecl, self) 59 self.ecl_runner = ECLMainRunner(ecl, self)
62 60
63 #TODO: The game calls it two times. What for?
64 # See 102h.exe@0x413220 if you think you're brave enough. 61 # See 102h.exe@0x413220 if you think you're brave enough.
65 self.prng.rand_uint16() 62 self.deaths_count = self.prng.rand_uint16() % 3
66 self.prng.rand_uint16() 63 self.next_bonus = self.prng.rand_uint16() % 8
67 64
68 65
69 def drop_bonus(self, x, y, _type, end_pos=None): 66 def drop_bonus(self, x, y, _type, end_pos=None):
70 player = self.players[0] #TODO 67 player = self.players[0] #TODO
71 if _type > 6: 68 if _type > 6:
85 def new_death(self, pos, index): 82 def new_death(self, pos, index):
86 anim = {0: 3, 1: 4, 2: 5}[index % 256] # The TB is wanted, if index isn’t in these values the original game crashs. 83 anim = {0: 3, 1: 4, 2: 5}[index % 256] # The TB is wanted, if index isn’t in these values the original game crashs.
87 self.effects.append(Effect(pos, anim, self.etama4)) 84 self.effects.append(Effect(pos, anim, self.etama4))
88 85
89 86
90 def new_particle(self, pos, color, size, amp, delay=False): 87 def new_particle(self, pos, color, size, amp):
91 self.effects.append(Particle(pos, 7 + 4 * color + self.prng.rand_uint16() % 4, self.etama4, size, amp, delay, self)) 88 self.effects.append(Particle(pos, 7 + 4 * color + self.prng.rand_uint16() % 4, self.etama4, size, amp, self))
92 89
93 90
94 def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score): 91 def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score):
95 enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm_wrapper, self) 92 enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm_wrapper, self)
96 self.enemies.append(enemy) 93 self.enemies.append(enemy)
106 self.effects = [enemy for enemy in self.effects if not enemy._removed] 103 self.effects = [enemy for enemy in self.effects if not enemy._removed]
107 self.bullets = [bullet for bullet in self.bullets if not bullet._removed] 104 self.bullets = [bullet for bullet in self.bullets if not bullet._removed]
108 self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if not bullet._removed] 105 self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if not bullet._removed]
109 self.items = [item for item in self.items if not item._removed] 106 self.items = [item for item in self.items if not item._removed]
110 107
108
111 # 3. Let's play! 109 # 3. Let's play!
112 #TODO: check update orders 110 # In the original game, updates are done in prioritized functions called "chains"
111 # We have to mimic this functionnality to be replay-compatible with the official game.
112
113 # Pri 6 is background
114 self.update_players(keystate) # Pri 7
115 self.update_enemies() # Pri 9
116 self.update_effects() # Pri 10
117 self.update_bullets() # Pri 11
118 # Pri 12 is HUD
119
120 # 4. Cleaning
121 self.cleanup()
122
123 self.frame += 1
124
125
126 def update_enemies(self):
127 for enemy in self.enemies:
128 enemy.update()
129
130 # Check for collisions
131 for enemy in self.enemies:
132 ex, ey = enemy.x, enemy.y
133 ehalf_size_x, ehalf_size_y = enemy.hitbox_half_size
134 ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x
135 ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y
136
137 for bullet in self.players_bullets:
138 half_size = bullet.hitbox_half_size
139 bx, by = bullet.x, bullet.y
140 bx1, bx2 = bx - half_size, bx + half_size
141 by1, by2 = by - half_size, by + half_size
142
143 if not (bx2 < ex1 or bx1 > ex2
144 or by2 < ey1 or by1 > ey2):
145 bullet.collide()
146 enemy.on_attack(bullet)
147 player.state.score += 90 # found experimentally
148
149
150 def update_players(self, keystate):
113 for player in self.players: 151 for player in self.players:
114 player.update(keystate) #TODO: differentiate keystates (multiplayer mode) 152 player.update(keystate) #TODO: differentiate keystates (multiplayer mode)
115 if player.state.x < 8.: 153 if player.state.x < 8.:
116 player.state.x = 8. 154 player.state.x = 8.
117 if player.state.x > 384.-8: #TODO 155 if player.state.x > 384.-8: #TODO
119 if player.state.y < 16.: 157 if player.state.y < 16.:
120 player.state.y = 16. 158 player.state.y = 16.
121 if player.state.y > 448.-16: #TODO 159 if player.state.y > 448.-16: #TODO
122 player.state.y = 448.-16 160 player.state.y = 448.-16
123 161
124 for enemy in self.enemies: 162 for bullet in self.players_bullets:
125 enemy.update() 163 bullet.update()
126 164
127 for enemy in self.effects: 165 # Check for collisions
128 enemy.update() 166 for player in self.players:
167 if not player.state.touchable:
168 continue
169
170 px, py = player.x, player.y
171 phalf_size = player.hitbox_half_size
172 px1, px2 = px - phalf_size, px + phalf_size
173 py1, py2 = py - phalf_size, py + phalf_size
174
175 ghalf_size = player.graze_hitbox_half_size
176 gx1, gx2 = px - ghalf_size, px + ghalf_size
177 gy1, gy2 = py - ghalf_size, py + ghalf_size
178
179 #TODO: Should that be done here or in update_enemies?
180 for enemy in self.enemies:
181 half_size_x, half_size_y = enemy.hitbox_half_size
182 bx, by = enemy.x, enemy.y
183 bx1, bx2 = bx - half_size_x, bx + half_size_x
184 by1, by2 = by - half_size_y, by + half_size_y
185
186 #TODO: box-box or point-in-box?
187 if enemy.touchable and not (bx2 < px1 or bx1 > px2
188 or by2 < py1 or by1 > py2):
189 enemy.on_collide()
190 if player.state.invulnerable_time == 0:
191 player.collide()
192
193
194 def update_effects(self):
195 for effect in self.effects:
196 effect.update()
197
198
199 def update_bullets(self):
200 for bullet in self.cancelled_bullets:
201 bullet.update()
129 202
130 for bullet in self.bullets: 203 for bullet in self.bullets:
131 bullet.update() 204 bullet.update()
132 205
133 for bullet in self.cancelled_bullets:
134 bullet.update()
135
136 for bullet in self.players_bullets:
137 bullet.update()
138
139 for item in self.items: 206 for item in self.items:
140 item.update() 207 item.update()
141 208
142 # 4. Check for collisions!
143 #TODO
144 for player in self.players: 209 for player in self.players:
145 if not player.state.touchable: 210 if not player.state.touchable:
146 continue 211 continue
147 212
148 px, py = player.x, player.y 213 px, py = player.x, player.y
169 elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2 234 elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2
170 or by2 < gy1 or by1 > gy2): 235 or by2 < gy1 or by1 > gy2):
171 bullet.grazed = True 236 bullet.grazed = True
172 player.state.graze += 1 237 player.state.graze += 1
173 player.state.score += 500 # found experimentally 238 player.state.score += 500 # found experimentally
174 self.new_particle((px, py), 0, .8, 192, delay=True) #TODO: find the real size and range. 239 self.new_particle((px, py), 0, .8, 192) #TODO: find the real size and range.
175 #TODO: display a static particle during one frame at 240 #TODO: display a static particle during one frame at
176 # 12 pixels of the player, in the axis of the “collision”. 241 # 12 pixels of the player, in the axis of the “collision”.
177
178 for enemy in self.enemies:
179 half_size_x, half_size_y = enemy.hitbox_half_size
180 bx, by = enemy.x, enemy.y
181 bx1, bx2 = bx - half_size_x, bx + half_size_x
182 by1, by2 = by - half_size_y, by + half_size_y
183
184 if enemy.touchable and not (bx2 < px1 or bx1 > px2
185 or by2 < py1 or by1 > py2):
186 enemy.on_collide()
187 if player.state.invulnerable_time == 0:
188 player.collide()
189 242
190 for item in self.items: 243 for item in self.items:
191 half_size = item.hitbox_half_size 244 half_size = item.hitbox_half_size
192 bx, by = item.x, item.y 245 bx, by = item.x, item.y
193 bx1, bx2 = bx - half_size, bx + half_size 246 bx1, bx2 = bx - half_size, bx + half_size
194 by1, by2 = by - half_size, by + half_size 247 by1, by2 = by - half_size, by + half_size
195 248
196 if not (bx2 < px1 or bx1 > px2 249 if not (bx2 < px1 or bx1 > px2
197 or by2 < py1 or by1 > py2): 250 or by2 < py1 or by1 > py2):
198 player.collect(item) 251 player.collect(item)
199
200 for enemy in self.enemies:
201 ex, ey = enemy.x, enemy.y
202 ehalf_size_x, ehalf_size_y = enemy.hitbox_half_size
203 ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x
204 ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y
205
206 for bullet in self.players_bullets:
207 half_size = bullet.hitbox_half_size
208 bx, by = bullet.x, bullet.y
209 bx1, bx2 = bx - half_size, bx + half_size
210 by1, by2 = by - half_size, by + half_size
211
212 if not (bx2 < ex1 or bx1 > ex2
213 or by2 < ey1 or by1 > ey2):
214 bullet.collide()
215 enemy.on_attack(bullet)
216 player.state.score += 90 # found experimentally
217
218 # 5. Cleaning
219 self.cleanup()
220
221 self.frame += 1
222 252
223 253
224 def cleanup(self): 254 def cleanup(self):
225 # Filter out non-visible enemies 255 # Filter out non-visible enemies
226 for enemy in tuple(self.enemies): 256 for enemy in tuple(self.enemies):