view pytouhou/game/enemymanager.py @ 52:ab826bc29aa2

Add some documentation, GPLv3 headers, README and COPYING file.
author Thibaut Girka <thib@sitedethib.com>
date Mon, 22 Aug 2011 22:37:14 +0200
parents 811cefefb5c8
children 299de3a9b69f
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 itertools import chain
from io import BytesIO
import os
from struct import unpack, pack
from pytouhou.utils.interpolator import Interpolator
from pytouhou.game.eclrunner import ECLRunner
from pytouhou.game.sprite import Sprite
from math import cos, sin, atan2


class Enemy(object):
    def __init__(self, pos, life, _type, anm_wrapper):
        self._anm_wrapper = anm_wrapper
        self._anm = None
        self._sprite = None
        self._removed = False
        self._type = _type

        self.frame = 0

        self.x, self.y = pos
        self.life = life
        self.max_life = life
        self.pending_bullets = []
        self.bullet_attributes = None
        self.bullet_launch_offset = (0, 0)
        self.vulnerable = True
        self.death_callback = None
        self.low_life_callback = None
        self.low_life_trigger = None
        self.timeout = None
        self.remaining_lives = -1

        self.bullet_launch_interval = 0
        self.delay_attack = False

        self.death_anim = None
        self.movement_dependant_sprites = None
        self.direction = None
        self.interpolator = None #TODO
        self.angle = 0.
        self.speed = 0.
        self.rotation_speed = 0.
        self.acceleration = 0.

        self.hitbox = (0, 0)
        self.screen_box = None


    def set_bullet_attributes(self, bullet_anim, launch_anim, bullets_per_shot,
                              number_of_shots, speed, unknown, launch_angle,
                              angle, flags):
        self.bullet_attributes = (1, bullet_anim, launch_anim, bullets_per_shot,
                                  number_of_shots, speed, unknown, launch_angle,
                                  angle, flags)
        if not self.delay_attack:
            pass
            #TODO: actually fire


    def select_player(self, players):
        return players[0] #TODO


    def get_player_angle(self, player):
        return atan2(player.y - self.y, player.x - self.x)


    def set_anim(self, index):
        self._anm, self._sprite = self._anm_wrapper.get_sprite(index)


    def set_pos(self, x, y, z):
        self.x, self.y = x, y
        self.interpolator = Interpolator((x, y))
        self.interpolator.set_interpolation_start(self.frame, (x, y))


    def move_to(self, duration, x, y, z):
        self.interpolator.set_interpolation_end(self.frame + duration, (x, y))


    def is_visible(self, screen_width, screen_height):
        if not self._sprite:
            return False

        tx, ty, tw, th = self._sprite.texcoords
        if self._sprite.corner_relative_placement:
            raise Exception #TODO
        else:
            max_x = tw / 2.
            max_y = th / 2.
            min_x = -max_x
            min_y = -max_y

        if any((min_x >= screen_width - self.x,
                max_x <= -self.x,
                min_y >= screen_height - self.y,
                max_y <= -self.y)):
            return False
        return True


    def get_objects_by_texture(self):
        objects_by_texture = {}
        key = self._anm.first_name, self._anm.secondary_name
        if not key in objects_by_texture:
            objects_by_texture[key] = (0, [], [], [])
        vertices = tuple((x + self.x, y + self.y, z) for x, y, z in self._sprite._vertices)
        objects_by_texture[key][1].extend(vertices)
        objects_by_texture[key][2].extend(self._sprite._uvs)
        objects_by_texture[key][3].extend(self._sprite._colors)
        #TODO: effects/bullet launch
        return objects_by_texture


    def update(self, frame):
        x, y = self.x, self.y
        if self.interpolator and self.interpolator.update(self.frame):
            x, y = self.interpolator.values

        self.speed += self.acceleration #TODO: units? Execution order?
        self.angle += self.rotation_speed #TODO: units? Execution order?

        dx, dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed
        if self._type & 2:
            x -= dx
        else:
            x += dx
        y += dy

        if self.movement_dependant_sprites:
            #TODO: is that really how it works?
            if x < self.x:
                self.set_anim(self.movement_dependant_sprites[2])
                self.direction = -1
            elif x > self.x:
                self.set_anim(self.movement_dependant_sprites[3])
                self.direction = +1
            elif self.direction is not None:
                self.set_anim(self.movement_dependant_sprites[{-1: 0, +1:1}[self.direction]])
                self.direction = None


        if self.screen_box:
            xmin, ymin, xmax, ymax = self.screen_box
            x = max(xmin, min(x, xmax))
            y = max(ymin, min(y, ymax))


        self.x, self.y = x, y
        if self._sprite:
            changed = self._sprite.update()
            visible = self.is_visible(384, 448)
            if changed and visible:
                self._sprite.update_vertices_uvs_colors()
            elif not self._sprite.playing:
                visible = False
                self._sprite = None
        else:
            visible = False


        self.frame += 1
        return visible



class EnemyManager(object):
    def __init__(self, stage, anm_wrapper, ecl, game_state):
        self._game_state = game_state
        self.stage = stage
        self.anm_wrapper = anm_wrapper
        self.main = []
        self.ecl = ecl
        self.objects_by_texture = {}
        self.enemies = []
        self.processes = []

        # Populate main
        for frame, sub, instr_type, args in ecl.main:
            if not self.main or self.main[-1][0] < frame:
                self.main.append((frame, [(sub, instr_type, args)]))
            elif self.main[-1][0] == frame:
                self.main[-1][1].append((sub, instr_type, args))


    def update(self, frame):
        if self.main and self.main[0][0] == frame:
            for sub, instr_type, args in self.main.pop(0)[1]:
                if instr_type in (0, 2, 4, 6): # Normal/mirrored enemy
                    x, y, z, life, unknown1, unknown2, unknown3 = args
                    if instr_type & 4:
                        if x < -990: #102h.exe@0x411820
                            x = self._game_state.prng.rand_double() * 368
                        if y < -990: #102h.exe@0x41184b
                            y = self._game_state.prng.rand_double() * 416
                        if z < -990: #102h.exe@0x411881
                            y = self._game_state.prng.rand_double() * 800
                    enemy = Enemy((x, y), life, instr_type, self.anm_wrapper)
                    self.enemies.append(enemy)
                    self.processes.append(ECLRunner(self.ecl, sub, enemy, self._game_state))


        # Run processes
        self.processes[:] = (process for process in self.processes if process.run_iteration())

        # Filter of destroyed enemies
        self.enemies[:] = (enemy for enemy in self.enemies if not enemy._removed)

        # Update enemies
        visible_enemies = [enemy for enemy in self.enemies if enemy.update(frame)]

        # Add enemies to vertices/uvs
        self.objects_by_texture = {}
        for enemy in visible_enemies:
            if enemy.is_visible(384, 448): #TODO
                for key, (count, vertices, uvs, colors) in enemy.get_objects_by_texture().items():
                    if not key in self.objects_by_texture:
                        self.objects_by_texture[key] = (0, [], [], [])
                    self.objects_by_texture[key][1].extend(vertices)
                    self.objects_by_texture[key][2].extend(uvs)
                    self.objects_by_texture[key][3].extend(colors)
        for key, (nb_vertices, vertices, uvs, colors) in self.objects_by_texture.items():
            nb_vertices = len(vertices)
            vertices = pack('f' * (3 * nb_vertices), *chain(*vertices))
            uvs = pack('f' * (2 * nb_vertices), *chain(*uvs))
            colors = pack('B' * (4 * nb_vertices), *chain(*colors))
            self.objects_by_texture[key] = (nb_vertices, vertices, uvs, colors)