view pytouhou/game/game.py @ 182:20843875ad8f

(Hopefully) use difficulty as it should. The difficulty[0] (also called rank) varies from 0 to 32 and affects various parts of the game. The difficulty now impact those parts, but how it is modified during the gameplay is not clear. Such changes to the difficulty are not handled yet. [0] http://en.touhouwiki.net/wiki/Embodiment_of_Scarlet_Devil/Gameplay#Rank
author Thibaut Girka <thib@sitedethib.com>
date Tue, 25 Oct 2011 01:29:40 +0200
parents 184196480f59
children 54eb6b254b7b
line wrap: on
line source

# -*- 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.utils.random import Random

from pytouhou.vm.eclrunner import ECLMainRunner

from pytouhou.game.player import Player
from pytouhou.game.enemy import Enemy
from pytouhou.game.item import Item
from pytouhou.game.effect import Effect
from pytouhou.game.effect import Particle



class Game(object):
    def __init__(self, resource_loader, player_states, stage, rank, difficulty,
                 bullet_types, item_types, characters, nb_bullets_max=None):
        self.resource_loader = resource_loader

        self.nb_bullets_max = nb_bullets_max
        self.bullet_types = bullet_types
        self.item_types = item_types
        self.characters = characters

        self.players = [Player(player_state, characters[player_state.character], self) for player_state in player_states]
        self.enemies = []
        self.effects = []
        self.bullets = []
        self.cancelled_bullets = []
        self.players_bullets = []
        self.items = []

        self.stage = stage
        self.rank = rank
        self.difficulty = difficulty
        self.boss = None
        self.spellcard = None
        self.prng = Random()
        self.frame = 0

        self.enm_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage,
                                                                 'stg%denm2.anm' % stage))
        self.etama4 = resource_loader.get_anm_wrapper(('etama4.anm',))
        ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
        self.ecl_runner = ECLMainRunner(ecl, self)


    def drop_bonus(self, x, y, _type, end_pos=None):
        player = self.players[0] #TODO
        if _type > 6:
            return
        item_type = self.item_types[_type]
        item = Item((x, y), item_type, self, end_pos=end_pos)
        self.items.append(item)


    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), item_type, self, player=player) for bullet in self.bullets)
        self.bullets = []


    def new_death(self, pos, index):
        anim = {0: 3, 1: 4, 2: 5}[index % 256] # The TB is wanted, if index isn’t in these values the original game crashs.
        self.effects.append(Effect(pos, anim, self.etama4))


    def new_particle(self, pos, color, size, amp):
        self.effects.append(Particle(pos, 7 + 4 * color + self.prng.rand_uint16() % 4, self.etama4, size,
                                     (pos[0] + amp * self.prng.rand_double() - amp/2,
                                      pos[1] + amp * self.prng.rand_double() - amp/2)))


    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_wrapper, self)
        self.enemies.append(enemy)
        return enemy


    def run_iter(self, keystate):
        # 1. VMs.
        self.ecl_runner.run_iter()

        # 2. Filter out destroyed enemies
        self.enemies = [enemy for enemy in self.enemies if not enemy._removed]
        self.effects = [enemy for enemy in self.effects if not enemy._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]

        # 3. Let's play!
        #TODO: check update orders
        for player in self.players:
            player.update(keystate) #TODO: differentiate keystates (multiplayer mode)
            if player.state.x < 8.:
                player.state.x = 8.
            if player.state.x > 384.-8: #TODO
                player.state.x = 384.-8
            if player.state.y < 16.:
                player.state.y = 16.
            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:
            bullet.update()

        for bullet in self.cancelled_bullets:
            bullet.update()

        for bullet in self.players_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

            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

            for bullet in self.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 < 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.score += 500 # found experimentally
                    self.new_particle((px, py), 0, .8, 192)
                    #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
                bx1, bx2 = bx - half_size, bx + half_size
                by1, by2 = by - half_size, by + half_size

                if not (bx2 < px1 or bx1 > px2
                        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
        for enemy in tuple(self.enemies):
            if enemy.is_visible(384, 448): #TODO
                enemy._was_visible = True
            elif enemy._was_visible:
                # Filter out-of-screen enemy
                enemy._removed = True
                self.enemies.remove(enemy)

        # Filter out-of-scren bullets
        # TODO: was_visible thing
        self.bullets = [bullet for bullet in self.bullets if bullet.is_visible(384, 448)]
        self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if bullet.is_visible(384, 448)]
        self.players_bullets = [bullet for bullet in self.players_bullets if bullet.is_visible(384, 448)]

        # Filter out-of-scren items
        self.items = [item for item in self.items if item.y < 448]

        # Disable boss mode if it is dead/it has timeout
        if self.boss and self.boss._removed:
            self.boss = None