Mercurial > touhou
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): |