Mercurial > touhou
changeset 447:78e1c3864e73
Make pytouhou.game.game an extension type.
| author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
|---|---|
| date | Sat, 17 Aug 2013 06:29:53 +0200 |
| parents | 3a33ed7f3b85 |
| children | 3bc37791f0a2 |
| files | pytouhou/game/bullet.pxd pytouhou/game/bullet.pyx pytouhou/game/effect.pyx pytouhou/game/enemy.pxd pytouhou/game/enemy.pyx pytouhou/game/game.pxd pytouhou/game/game.py pytouhou/game/game.pyx pytouhou/game/item.pxd pytouhou/game/item.pyx pytouhou/game/player.pxd pytouhou/game/player.pyx |
| diffstat | 12 files changed, 555 insertions(+), 460 deletions(-) [+] |
line wrap: on
line diff
--- a/pytouhou/game/bullet.pxd Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/bullet.pxd Sat Aug 17 06:29:53 2013 +0200 @@ -1,15 +1,17 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.utils.interpolator cimport Interpolator cdef class Bullet(Element): cdef public unsigned long state, flags, frame, sprite_idx_offset, damage cdef public double dx, dy, angle, speed cdef public bint player_bullet, was_visible, grazed - cdef public object target, _game, _bullet_type + cdef public object target, _bullet_type cdef public tuple hitbox cdef public list attributes cdef Interpolator speed_interpolator + cdef Game _game cdef bint is_visible(self, unsigned int screen_width, unsigned int screen_height) cpdef set_anim(self, sprite_idx_offset=*)
--- a/pytouhou/game/bullet.pyx Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/bullet.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -22,7 +22,7 @@ cdef class Bullet(Element): def __init__(self, pos, bullet_type, unsigned long sprite_idx_offset, - double angle, double speed, attributes, unsigned long flags, target, game, + double angle, double speed, attributes, unsigned long flags, target, Game game, bint player_bullet=False, unsigned long damage=0, hitbox=None): cdef double launch_mult
--- a/pytouhou/game/effect.pyx Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/effect.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -15,6 +15,8 @@ from pytouhou.game.sprite cimport Sprite from pytouhou.vm.anmrunner import ANMRunner +from pytouhou.game.game cimport Game + cdef class Effect(Element): def __init__(self, pos, index, anm): @@ -35,7 +37,7 @@ cdef class Particle(Effect): - def __init__(self, pos, index, anm, long amp, game, bint reverse=False, long duration=24): + def __init__(self, pos, index, anm, long amp, Game game, bint reverse=False, long duration=24): Effect.__init__(self, pos, index, anm) self.frame = 0
--- a/pytouhou/game/enemy.pxd Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/enemy.pxd Sat Aug 17 06:29:53 2013 +0200 @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.utils.interpolator cimport Interpolator cdef class Enemy(Element): @@ -9,8 +10,9 @@ cdef public dict laser_by_id cdef public list aux_anm cdef public Interpolator interpolator, speed_interpolator - cdef public object _game, _anms, process + cdef public object _anms, process + cdef Game _game cdef double[2] hitbox_half_size cpdef play_sound(self, index) @@ -34,7 +36,7 @@ cpdef set_pos(self, x, y, z) cpdef move_to(self, duration, x, y, z, formula) cpdef stop_in(self, duration, formula) - cpdef bint is_visible(self, long screen_width, long screen_height) + cdef bint is_visible(self, long screen_width, long screen_height) cdef void check_collisions(self) cdef void handle_callbacks(self) cpdef update(self)
--- a/pytouhou/game/enemy.pyx Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/enemy.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -22,7 +22,7 @@ cdef class Enemy(Element): - def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, game): + def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, Game game): Element.__init__(self) self._game = game @@ -272,7 +272,7 @@ formula) - cpdef bint is_visible(self, long screen_width, long screen_height): + cdef bint is_visible(self, long screen_width, long screen_height): cdef double tw, th if self.sprite is not None:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/game/game.pxd Sat Aug 17 06:29:53 2013 +0200 @@ -0,0 +1,28 @@ +from pytouhou.game.effect cimport Effect +from pytouhou.game.player cimport Player + +cdef class Game: + cdef public long width, height, nb_bullets_max, stage, rank, difficulty, difficulty_counter, difficulty_min, difficulty_max, frame, last_keystate + cdef public list bullet_types, laser_types, item_types, players, enemies, effects, bullets, lasers, cancelled_bullets, players_bullets, players_lasers, items, labels, faces, texts, hints, bonus_list + cdef public object interface, boss, msg_runner, prng, sfx_player + cdef public double continues + cdef public Effect spellcard_effect + cdef public tuple spellcard + cdef public bint time_stop, msg_wait + cdef public unsigned short deaths_count, next_bonus + + cpdef modify_difficulty(self, long diff) + cpdef drop_bonus(self, double x, double y, long _type, end_pos=*) + cdef void autocollect(self, Player player) + cpdef cancel_bullets(self) + cpdef new_particle(self, pos, long anim, long amp, long number=*, bint reverse=*, long duration=*) + cpdef new_label(self, pos, str text) + cdef void update_background(self) + cdef void update_enemies(self) + cdef void update_msg(self, long keystate) except * + cdef void update_players(self, long keystate) except * + cdef void update_effects(self) + cdef void update_hints(self) + cdef void update_faces(self) + cdef void update_bullets(self) + cdef void cleanup(self)
--- a/pytouhou/game/game.py Sat Aug 17 06:01:45 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,443 +0,0 @@ -# -*- encoding: utf-8 -*- -## -## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## - -from itertools import chain - -from pytouhou.vm.msgrunner import MSGRunner - -from pytouhou.game.bullet import LAUNCHED, CANCELLED -from pytouhou.game.enemy import Enemy -from pytouhou.game.item import Item -from pytouhou.game.effect import Effect, Particle -from pytouhou.game.text import Text -from pytouhou.game.face import Face - - -class Game(object): - def __init__(self, players, stage, rank, difficulty, bullet_types, - laser_types, item_types, nb_bullets_max=None, width=384, - height=448, prng=None, interface=None, continues=0, - hints=None): - self.width, self.height = width, height - - self.nb_bullets_max = nb_bullets_max - self.bullet_types = bullet_types - self.laser_types = laser_types - self.item_types = item_types - - self.players = players - self.enemies = [] - self.effects = [] - self.bullets = [] - self.lasers = [] - self.cancelled_bullets = [] - self.players_bullets = [] - self.players_lasers = [None, None] - self.items = [] - self.labels = [] - self.faces = [None, None] - self.interface = interface - self.hints = hints - - self.continues = continues - self.stage = stage - self.rank = rank - self.difficulty = difficulty - self.difficulty_counter = 0 - self.difficulty_min = 12 if rank == 0 else 10 - self.difficulty_max = 20 if rank == 0 else 32 - self.boss = None - self.spellcard = None - self.time_stop = False - self.msg_runner = None - self.msg_wait = False - 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 - self.frame = 0 - - self.spellcard_effect = None - - # See 102h.exe@0x413220 if you think you're brave enough. - self.deaths_count = self.prng.rand_uint16() % 3 - self.next_bonus = self.prng.rand_uint16() % 8 - - self.last_keystate = 0 - - - def msg_sprites(self): - return [face for face in self.faces if face] if self.msg_runner and not self.msg_runner.ended else [] - - - def lasers_sprites(self): - return [laser for laser in self.players_lasers if laser] - - - def modify_difficulty(self, diff): - self.difficulty_counter += diff - while self.difficulty_counter < 0: - self.difficulty -= 1 - self.difficulty_counter += 100 - while self.difficulty_counter >= 100: - self.difficulty += 1 - self.difficulty_counter -= 100 - if self.difficulty < self.difficulty_min: - self.difficulty = self.difficulty_min - elif self.difficulty > self.difficulty_max: - self.difficulty = self.difficulty_max - - - def enable_spellcard_effect(self): - self.spellcard_effect = Effect((-32., -16.), 0, - self.spellcard_effect_anm) #TODO: find why this offset is necessary. - self.spellcard_effect.sprite.allow_dest_offset = True #TODO: should be the role of anm’s 25th instruction. Investigate! - - - def disable_spellcard_effect(self): - self.spellcard_effect = None - - - def drop_bonus(self, x, y, _type, end_pos=None): - player = self.players[0] #TODO - if _type > 6: - return - if len(self.items) >= self.nb_bullets_max: - return #TODO: check - item_type = self.item_types[_type] - item = Item((x, y), _type, item_type, self, end_pos=end_pos) - self.items.append(item) - - - def autocollect(self, player): - for item in self.items: - item.autocollect(player) - - - def cancel_bullets(self): - for bullet in self.bullets: - bullet.cancel() - for laser in self.lasers: - laser.cancel() - - - def change_bullets_into_star_items(self): - player = self.players[0] #TODO - item_type = self.item_types[6] - self.items.extend(Item((bullet.x, bullet.y), 6, item_type, self, player=player) - for bullet in self.bullets) - for laser in self.lasers: - self.items.extend(Item(pos, 6, item_type, self, player=player) - for pos in laser.get_bullets_pos()) - laser.cancel() - self.bullets = [] - - - def change_bullets_into_bonus(self): - player = self.players[0] #TODO - score = 0 - bonus = 2000 - for bullet in self.bullets: - self.new_label((bullet.x, bullet.y), str(bonus)) - score += bonus - bonus += 10 - self.bullets = [] - player.state.score += score - #TODO: display the final bonus score. - - - def kill_enemies(self): - for enemy in self.enemies: - if enemy.boss: - pass # Bosses are immune to 96 - elif enemy.touchable: - enemy.life = 0 - elif enemy.death_callback > 0: - #TODO: check - enemy.process.switch_to_sub(enemy.death_callback) - enemy.death_callback = -1 - - - def new_effect(self, pos, anim, anm=None, number=1): - number = min(number, self.nb_bullets_max - len(self.effects)) - for i in xrange(number): - self.effects.append(Effect(pos, anim, anm or self.etama[1])) - - - def new_particle(self, pos, anim, amp, number=1, reverse=False, duration=24): - number = min(number, self.nb_bullets_max - len(self.effects)) - for i in xrange(number): - self.effects.append(Particle(pos, anim, self.etama[1], amp, self, reverse=reverse, duration=duration)) - - - def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score): - enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm, self) - self.enemies.append(enemy) - return enemy - - - def new_msg(self, sub): - self.msg_runner = MSGRunner(self.msg, sub, self) - self.msg_runner.run_iteration() - - - def new_label(self, pos, text): - label = Text(pos, self.interface.ascii_anm, text=text, xspacing=8, shift=48) - label.set_timeout(60, effect='move') - self.labels.append(label) - return label - - - def new_hint(self, hint): - pos = hint['Pos'] - #TODO: Scale - - pos = pos[0] + 192, pos[1] - label = Text(pos, self.interface.ascii_anm, text=hint['Text'], align=hint['Align']) - label.set_timeout(hint['Time']) - label.set_alpha(hint['Alpha']) - label.set_color(hint['Color'], text=False) - self.labels.append(label) - return label - - - def new_face(self, side, effect): - face = Face(self.msg_anm, effect, side) - self.faces[side] = face - return face - - - def run_iter(self, keystate): - # 1. VMs. - for runner in self.ecl_runners: - runner.run_iter() - - # 2. Modify difficulty - if self.frame % (32*60) == (32*60): #TODO: check if that is really that frame. - self.modify_difficulty(+100) - - # 3. Filter out destroyed enemies - self.enemies = [enemy for enemy in self.enemies if not enemy.removed] - self.effects = [effect for effect in self.effects if not effect.removed] - self.bullets = [bullet for bullet in self.bullets if not bullet.removed] - 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] - - - # 4. Let's play! - # 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_background() #TODO: Pri unknown - if self.msg_runner: - self.update_msg(keystate) # Pri ? - keystate &= ~3 # Remove the ability to attack (keystates 1 and 2). - self.update_players(keystate) # Pri 7 - self.update_enemies() # Pri 9 - self.update_effects() # Pri 10 - self.update_bullets() # Pri 11 - for laser in self.lasers: #TODO: what priority is it? - laser.update() - self.interface.update() # Pri 12 - if self.hints: - self.update_hints() # Not from this game, so unknown. - for label in self.labels: #TODO: what priority is it? - label.update() - self.update_faces() # Pri XXX - - # 5. Clean up - self.cleanup() - - self.frame += 1 - - - def update_background(self): - if self.time_stop: - return None - if self.spellcard_effect is not None: - self.spellcard_effect.update() - #TODO: update the actual background here? - - - def update_enemies(self): - for enemy in self.enemies: - enemy.update() - - - def update_msg(self, keystate): - if any((keystate & k and not self.last_keystate & k) for k in (1, 256)): - self.msg_runner.skip() - self.msg_runner.skipping = bool(keystate & 256) - self.last_keystate = keystate - self.msg_runner.run_iteration() - - - def update_players(self, keystate): - if self.time_stop: - return None - - for bullet in self.players_bullets: - bullet.update() - - for player in self.players: - player.update(keystate) #TODO: differentiate keystates (multiplayer mode) - - #XXX: Why 78910? Is it really the right value? - player.state.effective_score = min(player.state.effective_score + 78910, - player.state.score) - #TODO: give extra lives to the player - - - def update_effects(self): - for effect in self.effects: - effect.update() - - - def update_hints(self): - for hint in self.hints: - if hint['Count'] == self.frame and hint['Base'] == 'start': - self.new_hint(hint) - - - def update_faces(self): - for face in self.faces: - if face: - face.update() - - - def update_bullets(self): - if self.time_stop: - return None - for bullet in self.cancelled_bullets: - bullet.update() - - for bullet in self.bullets: - bullet.update() - - for laser in self.players_lasers: - if laser: - laser.update() - - for item in self.items: - item.update() - - for player in self.players: - if not player.state.touchable: - continue - - px, py = player.state.x, player.state.y - phalf_size = player.sht.hitbox - px1, px2 = px - phalf_size, px + phalf_size - py1, py2 = py - phalf_size, py + phalf_size - - ghalf_size = player.sht.graze_hitbox - gx1, gx2 = px - ghalf_size, px + ghalf_size - gy1, gy2 = py - ghalf_size, py + ghalf_size - - for laser in self.lasers: - if laser.check_collision((px, py)): - if player.state.invulnerable_time == 0: - player.collide() - elif laser.check_grazing((px, py)): - player.state.graze += 1 #TODO - player.state.score += 500 #TODO - player.play_sound('graze') - self.modify_difficulty(+6) #TODO - self.new_particle((px, py), 9, 192) #TODO - - for bullet in self.bullets: - if bullet.state != LAUNCHED: - continue - - bhalf_width, bhalf_height = bullet.hitbox - bx, by = bullet.x, bullet.y - bx1, bx2 = bx - bhalf_width, bx + bhalf_width - by1, by2 = by - bhalf_height, by + bhalf_height - - if not (bx2 < px1 or bx1 > px2 - or by2 < py1 or by1 > py2): - bullet.collide() - if player.state.invulnerable_time == 0: - player.collide() - - elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2 - or by2 < gy1 or by1 > gy2): - bullet.grazed = True - player.state.graze += 1 - player.state.score += 500 # found experimentally - player.play_sound('graze') - self.modify_difficulty(+6) - self.new_particle((px, py), 9, 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”. - - #TODO: is it the right place? - if py < 128 and player.state.power >= 128: #TODO: check py. - self.autocollect(player) - - ihalf_size = player.sht.item_hitbox - for item in self.items: - bx, by = item.x, item.y - bx1, bx2 = bx - ihalf_size, bx + ihalf_size - by1, by2 = by - ihalf_size, by + ihalf_size - - if not (bx2 < px1 or bx1 > px2 - or by2 < py1 or by1 > py2): - item.on_collect(player) - - - def cleanup(self): - # Filter out non-visible enemies - for enemy in self.enemies: - if enemy.is_visible(self.width, self.height): - enemy.was_visible = True - elif enemy.was_visible: - # Filter out-of-screen enemy - enemy.removed = True - - self.enemies = [enemy for enemy in self.enemies if not enemy.removed] - - # Update cancelled bullets - self.cancelled_bullets = [b for b in chain(self.cancelled_bullets, - self.bullets, - self.players_bullets) - if b.state == CANCELLED and not b.removed] - # Filter out-of-scren bullets - self.bullets = [bullet for bullet in self.bullets - if not bullet.removed and bullet.state != CANCELLED] - self.players_bullets = [bullet for bullet in self.players_bullets - if not bullet.removed and bullet.state != CANCELLED] - for i, laser in enumerate(self.players_lasers): - if laser and laser.removed: - self.players_lasers[i] = None - self.effects = [effect for effect in self.effects if not effect.removed] - - # Filter “timed-out” lasers - self.lasers = [laser for laser in self.lasers if not laser.removed] - - # Filter out-of-scren items - items = [] - for item in self.items: - if item.y < self.height: - items.append(item) - else: - self.modify_difficulty(-3) - self.items = items - - self.labels = [label for label in self.labels if not label.removed] - - # Disable boss mode if it is dead/it has timeout - if self.boss and self.boss._enemy.removed: - self.boss = None -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/game/game.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -0,0 +1,501 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +from pytouhou.vm.msgrunner import MSGRunner + +from pytouhou.game.element cimport Element +from pytouhou.game.bullet cimport Bullet +from pytouhou.game.bullet import LAUNCHED, CANCELLED +from pytouhou.game.enemy cimport Enemy +from pytouhou.game.item cimport Item +from pytouhou.game.effect cimport Particle +from pytouhou.game.text import Text +from pytouhou.game.face import Face + + +cdef class Game: + def __init__(self, players, long stage, long rank, long difficulty, bullet_types, + laser_types, item_types, long nb_bullets_max=0, long width=384, + long height=448, prng=None, interface=None, double continues=0, + hints=None): + self.width, self.height = width, height + + self.nb_bullets_max = nb_bullets_max + self.bullet_types = bullet_types + self.laser_types = laser_types + self.item_types = item_types + + self.players = players + self.enemies = [] + self.effects = [] + self.bullets = [] + self.lasers = [] + self.cancelled_bullets = [] + self.players_bullets = [] + self.players_lasers = [None, None] + self.items = [] + self.labels = [] + self.faces = [None, None] + self.interface = interface + self.hints = hints + + self.continues = continues + self.stage = stage + self.rank = rank + self.difficulty = difficulty + self.difficulty_counter = 0 + self.difficulty_min = 12 if rank == 0 else 10 + self.difficulty_max = 20 if rank == 0 else 32 + self.boss = None + self.spellcard = None + self.time_stop = False + self.msg_runner = None + self.msg_wait = False + 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 + self.frame = 0 + self.sfx_player = None + + self.spellcard_effect = None + + # See 102h.exe@0x413220 if you think you're brave enough. + self.deaths_count = self.prng.rand_uint16() % 3 + self.next_bonus = self.prng.rand_uint16() % 8 + + self.last_keystate = 0 + + + def msg_sprites(self): + return [face for face in self.faces if face is not None] if self.msg_runner is not None and not self.msg_runner.ended else [] + + + def lasers_sprites(self): + return [laser for laser in self.players_lasers if laser is not None] + + + cpdef modify_difficulty(self, long diff): + self.difficulty_counter += diff + while self.difficulty_counter < 0: + self.difficulty -= 1 + self.difficulty_counter += 100 + while self.difficulty_counter >= 100: + self.difficulty += 1 + self.difficulty_counter -= 100 + if self.difficulty < self.difficulty_min: + self.difficulty = self.difficulty_min + elif self.difficulty > self.difficulty_max: + self.difficulty = self.difficulty_max + + + def enable_spellcard_effect(self): + self.spellcard_effect = Effect((-32., -16.), 0, + self.spellcard_effect_anm) #TODO: find why this offset is necessary. + self.spellcard_effect.sprite.allow_dest_offset = True #TODO: should be the role of anm’s 25th instruction. Investigate! + + + def disable_spellcard_effect(self): + self.spellcard_effect = None + + + cpdef drop_bonus(self, double x, double y, long _type, end_pos=None): + if _type > 6: + return + if len(self.items) >= self.nb_bullets_max: + return #TODO: check + item_type = self.item_types[_type] + self.items.append(Item((x, y), _type, item_type, self, end_pos=end_pos)) + + + cdef void autocollect(self, Player player): + cdef Item item + + for item in self.items: + item.autocollect(player) + + + cpdef cancel_bullets(self): + cdef Bullet bullet + #TODO: cdef Laser laser + + for bullet in self.bullets: + bullet.cancel() + for laser in self.lasers: + laser.cancel() + + + def change_bullets_into_star_items(self): + cdef Player player + cdef Bullet bullet + + player = self.players[0] #TODO + item_type = self.item_types[6] + self.items.extend(Item((bullet.x, bullet.y), 6, item_type, self, player=player) + for bullet in self.bullets) + for laser in self.lasers: + self.items.extend(Item(pos, 6, item_type, self, player=player) + for pos in laser.get_bullets_pos()) + laser.cancel() + self.bullets = [] + + + def change_bullets_into_bonus(self): + cdef Player player + cdef Bullet bullet + + player = self.players[0] #TODO + score = 0 + bonus = 2000 + for bullet in self.bullets: + self.new_label((bullet.x, bullet.y), str(bonus)) + score += bonus + bonus += 10 + self.bullets = [] + player.state.score += score + #TODO: display the final bonus score. + + + def kill_enemies(self): + cdef Enemy enemy + + for enemy in self.enemies: + if enemy.boss: + pass # Bosses are immune to 96 + elif enemy.touchable: + enemy.life = 0 + elif enemy.death_callback > 0: + #TODO: check + enemy.process.switch_to_sub(enemy.death_callback) + enemy.death_callback = -1 + + + def new_effect(self, pos, long anim, anm=None, long number=1): + number = min(number, self.nb_bullets_max - len(self.effects)) + for i in xrange(number): + self.effects.append(Effect(pos, anim, anm or self.etama[1])) + + + cpdef new_particle(self, pos, long anim, long amp, long number=1, bint reverse=False, long duration=24): + number = min(number, self.nb_bullets_max - len(self.effects)) + for i in xrange(number): + self.effects.append(Particle(pos, anim, self.etama[1], amp, self, reverse=reverse, duration=duration)) + + + def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score): + enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm, self) + self.enemies.append(enemy) + return enemy + + + def new_msg(self, sub): + self.msg_runner = MSGRunner(self.msg, sub, self) + self.msg_runner.run_iteration() + + + cpdef new_label(self, pos, str text): + label = Text(pos, self.interface.ascii_anm, text=text, xspacing=8, shift=48) + label.set_timeout(60, effect='move') + self.labels.append(label) + return label + + + def new_hint(self, hint): + pos = hint['Pos'] + #TODO: Scale + + pos = pos[0] + 192, pos[1] + label = Text(pos, self.interface.ascii_anm, text=hint['Text'], align=hint['Align']) + label.set_timeout(hint['Time']) + label.set_alpha(hint['Alpha']) + label.set_color(hint['Color'], text=False) + self.labels.append(label) + return label + + + def new_face(self, side, effect): + face = Face(self.msg_anm, effect, side) + self.faces[side] = face + return face + + + def run_iter(self, long keystate): + # 1. VMs. + for runner in self.ecl_runners: + runner.run_iter() + + # 2. Modify difficulty + if self.frame % (32*60) == (32*60): #TODO: check if that is really that frame. + self.modify_difficulty(+100) + + # 3. Filter out destroyed enemies + self.enemies = filter_removed(self.enemies) + self.effects = filter_removed(self.effects) + self.bullets = filter_removed(self.bullets) + self.cancelled_bullets = filter_removed(self.cancelled_bullets) + self.items = filter_removed(self.items) + + # 4. Let's play! + # 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_background() #TODO: Pri unknown + if self.msg_runner is not None: + self.update_msg(keystate) # Pri ? + keystate &= ~3 # Remove the ability to attack (keystates 1 and 2). + self.update_players(keystate) # Pri 7 + self.update_enemies() # Pri 9 + self.update_effects() # Pri 10 + self.update_bullets() # Pri 11 + for laser in self.lasers: #TODO: what priority is it? + laser.update() + self.interface.update() # Pri 12 + if self.hints: + self.update_hints() # Not from this game, so unknown. + for label in self.labels: #TODO: what priority is it? + label.update() + self.update_faces() # Pri XXX + + # 5. Clean up + self.cleanup() + + self.frame += 1 + + + cdef void update_background(self): + if self.time_stop: + return + if self.spellcard_effect is not None: + self.spellcard_effect.update() + #TODO: update the actual background here? + + + cdef void update_enemies(self): + cdef Enemy enemy + + for enemy in self.enemies: + enemy.update() + + + cdef void update_msg(self, long keystate) except *: + cdef long k + + if any([(keystate & k and not self.last_keystate & k) for k in (1, 256)]): + self.msg_runner.skip() + self.msg_runner.skipping = bool(keystate & 256) + self.last_keystate = keystate + self.msg_runner.run_iteration() + + + cdef void update_players(self, long keystate) except *: + cdef Bullet bullet + cdef Player player + + if self.time_stop: + return + + for bullet in self.players_bullets: + bullet.update() + + for player in self.players: + player.update(keystate) #TODO: differentiate keystates (multiplayer mode) + + #XXX: Why 78910? Is it really the right value? + player.state.effective_score = min(player.state.effective_score + 78910, + player.state.score) + #TODO: give extra lives to the player + + + cdef void update_effects(self): + cdef Element effect + + for effect in self.effects: + effect.update() + + + cdef void update_hints(self): + for hint in self.hints: + if hint['Count'] == self.frame and hint['Base'] == 'start': + self.new_hint(hint) + + + cdef void update_faces(self): + for face in self.faces: + if face: + face.update() + + + cdef void update_bullets(self): + cdef Player player + cdef Bullet bullet + cdef Item item + cdef double bhalf_width, bhalf_height + + if self.time_stop: + return + + for bullet in self.cancelled_bullets: + bullet.update() + + for bullet in self.bullets: + bullet.update() + + for laser in self.players_lasers: + if laser is not None: + laser.update() + + for item in self.items: + item.update() + + for player in self.players: + player_state = player.state + + if not player_state.touchable: + continue + + px, py = player_state.x, player_state.y + phalf_size = <double>player.sht.hitbox + px1, px2 = px - phalf_size, px + phalf_size + py1, py2 = py - phalf_size, py + phalf_size + + ghalf_size = <double>player.sht.graze_hitbox + gx1, gx2 = px - ghalf_size, px + ghalf_size + gy1, gy2 = py - ghalf_size, py + ghalf_size + + for laser in self.lasers: + if laser.check_collision((px, py)): + if player_state.invulnerable_time == 0: + player.collide() + elif laser.check_grazing((px, py)): + player_state.graze += 1 #TODO + player_state.score += 500 #TODO + player.play_sound('graze') + self.modify_difficulty(+6) #TODO + self.new_particle((px, py), 9, 192) #TODO + + for bullet in self.bullets: + if bullet.state != LAUNCHED: + continue + + bhalf_width, bhalf_height = bullet.hitbox + bx, by = bullet.x, bullet.y + bx1, bx2 = bx - bhalf_width, bx + bhalf_width + by1, by2 = by - bhalf_height, by + bhalf_height + + if not (bx2 < px1 or bx1 > px2 + or by2 < py1 or by1 > py2): + bullet.collide() + if player_state.invulnerable_time == 0: + player.collide() + + elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2 + or by2 < gy1 or by1 > gy2): + bullet.grazed = True + player_state.graze += 1 + player_state.score += 500 # found experimentally + player.play_sound('graze') + self.modify_difficulty(+6) + self.new_particle((px, py), 9, 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”. + + #TODO: is it the right place? + if py < 128 and player_state.power >= 128: #TODO: check py. + self.autocollect(player) + + ihalf_size = <double>player.sht.item_hitbox + for item in self.items: + bx, by = item.x, item.y + bx1, bx2 = bx - ihalf_size, bx + ihalf_size + by1, by2 = by - ihalf_size, by + ihalf_size + + if not (bx2 < px1 or bx1 > px2 + or by2 < py1 or by1 > py2): + item.on_collect(player) + + + cdef void cleanup(self): + cdef Enemy enemy + cdef Bullet bullet + cdef Item item + cdef long i + + # Filter out non-visible enemies + for enemy in self.enemies: + if enemy.is_visible(self.width, self.height): + enemy.was_visible = True + elif enemy.was_visible: + # Filter out-of-screen enemy + enemy.removed = True + + self.enemies = filter_removed(self.enemies) + + # Filter out-of-scren bullets + cancelled_bullets = [] + bullets = [] + players_bullets = [] + + for bullet in self.cancelled_bullets: + if bullet.state == CANCELLED and not bullet.removed: + cancelled_bullets.append(bullet) + + for bullet in self.bullets: + if not bullet.removed: + if bullet.state == CANCELLED: + cancelled_bullets.append(bullet) + else: + bullets.append(bullet) + + for bullet in self.players_bullets: + if not bullet.removed: + if bullet.state == CANCELLED: + cancelled_bullets.append(bullet) + else: + players_bullets.append(bullet) + + self.cancelled_bullets = cancelled_bullets + self.bullets = bullets + self.players_bullets = players_bullets + + # Filter “timed-out” lasers + for i, laser in enumerate(self.players_lasers): + if laser is not None and laser.removed: + self.players_lasers[i] = None + + self.lasers = filter_removed(self.lasers) + + # Filter out-of-scren items + items = [] + for item in self.items: + if item.y < self.height: + items.append(item) + else: + self.modify_difficulty(-3) + self.items = items + + self.effects = filter_removed(self.effects) + self.labels = filter_removed(self.labels) + + # Disable boss mode if it is dead/it has timeout + if self.boss and self.boss._enemy.removed: + self.boss = None + +cdef list filter_removed(list elements): + cdef Element element + + filtered = [] + for element in elements: + if not element.removed: + filtered.append(element) + return filtered
--- a/pytouhou/game/item.pxd Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/item.pxd Sat Aug 17 06:29:53 2013 +0200 @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game from pytouhou.game.player cimport Player from pytouhou.utils.interpolator cimport Interpolator @@ -12,10 +13,10 @@ cdef class Item(Element): cdef public object _item_type - cdef object _game cdef unsigned long frame cdef long _type cdef double angle, speed + cdef Game _game cdef Player player cdef Indicator indicator cdef Interpolator speed_interpolator, pos_interpolator
--- a/pytouhou/game/item.pyx Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/item.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -33,7 +33,7 @@ cdef class Item(Element): - def __init__(self, start_pos, long _type, item_type, game, double angle=pi/2, Player player=None, end_pos=None): + def __init__(self, start_pos, long _type, item_type, Game game, double angle=pi/2, Player player=None, end_pos=None): Element.__init__(self, start_pos) self._game = game
--- a/pytouhou/game/player.pxd Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/player.pxd Sat Aug 17 06:29:53 2013 +0200 @@ -1,4 +1,5 @@ from pytouhou.game.element cimport Element +from pytouhou.game.game cimport Game cdef class PlayerState: cdef public double x, y @@ -8,8 +9,8 @@ cdef class Player(Element): cdef public PlayerState state - cdef public object _game cdef public long death_time + cdef public Game _game cdef object anm cdef tuple speeds
--- a/pytouhou/game/player.pyx Sat Aug 17 06:01:45 2013 +0200 +++ b/pytouhou/game/player.pyx Sat Aug 17 06:29:53 2013 +0200 @@ -55,7 +55,7 @@ cdef class Player(Element): - def __init__(self, PlayerState state, game, anm): + def __init__(self, PlayerState state, Game game, anm): Element.__init__(self) self._game = game @@ -117,7 +117,7 @@ bullets = self._game.players_bullets lasers = self._game.players_lasers - nb_bullets_max = <long>self._game.nb_bullets_max + nb_bullets_max = self._game.nb_bullets_max if self.fire_time % 5 == 0: self.play_sound('plst00') @@ -231,7 +231,7 @@ self.fire_time -= 1 if self.death_time: - time = <long>self._game.frame - self.death_time + time = self._game.frame - self.death_time if time == 6: # too late, you are dead :( self.state.touchable = False if self.state.power > 16: @@ -277,6 +277,10 @@ self.sprite.fade(26, 96) self.sprite.scale_in(26, 0., 2.5) + #TODO: the next two branches could be happening at the same frame. + elif time == 31: + self._game.cancel_bullets() + elif time == 32: self.state.x = float(self._game.width) / 2. #TODO self.state.y = float(self._game.width) #TODO @@ -296,10 +300,7 @@ self.sprite.blendfunc = 0 self.sprite.changed = True - if time > 30: - self._game.cancel_bullets() - - if time > 90: # start the bullet hell again + elif time == 91: # start the bullet hell again self.death_time = 0 self.anmrunner.run_frame()
