view nagslang/protagonist.py @ 313:768e1d06155f

PEP8
author David Sharpe
date Fri, 06 Sep 2013 00:45:52 +0200
parents 72aca01c87ed
children 3dd32686dbc3
line wrap: on
line source

import pymunk
import pymunk.pygame_util
from pymunk.vec2d import Vec2d

from nagslang import render
from nagslang.constants import (
    COLLISION_TYPE_PLAYER, ZORDER_MID, WEREWOLF_SOAK_FACTOR,
    PROTAGONIST_HEALTH_MIN_LEVEL, PROTAGONIST_HEALTH_MAX_LEVEL,
    NON_GAME_OBJECT_COLLIDERS, COLLISION_TYPE_WEREWOLF_ATTACK)
from nagslang.events import FireEvent, ClawEvent
from nagslang.game_object import GameObject, Physicser, make_body
from nagslang.mutators import FLIP_H
from nagslang.resources import resources
from nagslang.events import ScreenChange


class ProtagonistPhysicser(Physicser):
    def __init__(self, space, form_shapes):
        self._space = space
        self._form_shapes = form_shapes

    def switch_form(self, old_form, new_form):
        self._space.remove(self._form_shapes[old_form])
        shape = self._form_shapes[new_form]
        self._space.add(shape)
        for attr, value in shape.protagonist_body_props.iteritems():
            setattr(shape.body, attr, value)

    def get_shape(self):
        return self._form_shapes[self.game_object.form]


class ProtagonistFormSelectionRenderer(render.RendererSelectionRenderer):
    def select_renderer(self):
        return self.game_object.form


class Protagonist(GameObject):
    """Representation of our fearless protagonist.

    TODO: Factor out a bunch of this stuff when we need it for other objects.
    """

    HUMAN_FORM = 'human'
    WOLF_FORM = 'wolf'

    def __init__(self, space, world, position):
        physicser = self._make_physics(space, position)
        renderer = self._make_renderer()
        self.inventory = {}
        self.form = self.HUMAN_FORM
        self.angle = 0
        self.is_moving = False
        self.world = world

        super(Protagonist, self).__init__(physicser, renderer)
        self.zorder = ZORDER_MID

        self.health_level = 100

        self.go_human()
        self._time = 0

    def _make_physics(self, space, position):
        body = make_body(10, pymunk.inf, position, 0.8)
        body.velocity_limit = 1000

        human = pymunk.Poly(body, [(-15, -30), (15, -30), (15, 30), (-15, 30)])
        human.elasticity = 1.0
        human.collision_type = COLLISION_TYPE_PLAYER
        human.protagonist_body_props = {
            'mass': 10,
            'damping': 0.8,
        }

        wolf = pymunk.Circle(body, 30)
        wolf.elasticity = 1.0
        wolf.collision_type = COLLISION_TYPE_PLAYER
        wolf.protagonist_body_props = {
            'mass': 100,
            'damping': 0.9,
        }

        return ProtagonistPhysicser(space, {
            self.HUMAN_FORM: human,
            self.WOLF_FORM: wolf,
        })

    def _get_image(self, name, *transforms):
        return resources.get_image('creatures', name, transforms=transforms)

    def change_space(self, new_space):
        self.physicser.remove_from_space()
        self.physicser.set_space(new_space)
        self.physicser.add_to_space()

    def reset(self):
        self.health_level = 100
        self.is_moving = False

        self.go_human()

    def _make_renderer(self):
        return ProtagonistFormSelectionRenderer({
            self.HUMAN_FORM: render.FacingSelectionRenderer(
                {
                    'N': render.MovementAnimatedRenderer(
                        [self._get_image('human_N_1.png'),
                         self._get_image('human_N_2.png')], 3),
                    'S': render.MovementAnimatedRenderer(
                        [self._get_image('human_S_1.png'),
                         self._get_image('human_S_2.png')], 3),
                    'W': render.MovementAnimatedRenderer(
                        [self._get_image('human_W_1.png'),
                         self._get_image('human_W_2.png')], 3),
                    'E': render.MovementAnimatedRenderer(
                        [self._get_image('human_W_1.png', FLIP_H),
                         self._get_image('human_W_2.png', FLIP_H)], 3),
                    'NW': render.MovementAnimatedRenderer(
                        [self._get_image('human_NW_1.png'),
                         self._get_image('human_NW_2.png')], 3),
                    'NE': render.MovementAnimatedRenderer(
                        [self._get_image('human_NW_1.png', FLIP_H),
                         self._get_image('human_NW_2.png', FLIP_H)], 3),
                    'SW': render.MovementAnimatedRenderer(
                        [self._get_image('human_SW_1.png'),
                         self._get_image('human_SW_2.png')], 3),
                    'SE': render.MovementAnimatedRenderer(
                        [self._get_image('human_SW_1.png', FLIP_H),
                         self._get_image('human_SW_2.png', FLIP_H)], 3),
                }),
            self.WOLF_FORM: render.FacingSelectionRenderer(
                {
                    'N': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_N_1.png'),
                         self._get_image('werewolf_N_2.png')], 3),
                    'S': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_S_1.png'),
                         self._get_image('werewolf_S_2.png')], 3),
                    'W': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_W_1.png'),
                         self._get_image('werewolf_W_2.png')], 3),
                    'E': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_W_1.png', FLIP_H),
                         self._get_image('werewolf_W_2.png', FLIP_H)], 3),
                    'NW': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_NW_1.png'),
                         self._get_image('werewolf_NW_2.png')], 3),
                    'NE': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_NW_1.png', FLIP_H),
                         self._get_image('werewolf_NW_2.png', FLIP_H)], 3),
                    'SW': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_SW_1.png'),
                         self._get_image('werewolf_SW_2.png')], 3),
                    'SE': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_SW_1.png', FLIP_H),
                         self._get_image('werewolf_SW_2.png', FLIP_H)], 3),
                }),
        })

    @classmethod
    def from_saved_state(cls, saved_state):
        """Create an instance from the provided serialised state.
        """
        obj = cls()
        # TODO: Update from saved state.
        return obj

    def get_render_angle(self):
        # No image rotation when rendering, please.
        return 0

    def get_facing_direction(self):
        # It's easier to work with a vector than an angle here.
        vec = Vec2d.unit()
        vec.angle = self.angle
        # We probably don't have exactly -1, 0, or 1 here.
        x = int(round(vec.x))
        y = int(round(vec.y))

        return {
            (0, 1): 'N',
            (0, -1): 'S',
            (-1, 0): 'W',
            (1, 0): 'E',
            (1, 1): 'NE',
            (1, -1): 'SE',
            (-1, 1): 'NW',
            (-1, -1): 'SW',
        }[(x, y)]

    def go_werewolf(self):
        self.physicser.switch_form(self.form, self.WOLF_FORM)
        self.form = self.WOLF_FORM
        self.impulse_factor = 4000

    def go_human(self):
        self.physicser.switch_form(self.form, self.HUMAN_FORM)
        self.form = self.HUMAN_FORM
        self.impulse_factor = 500

    def set_direction(self, dx, dy):
        if (dx, dy) == (0, 0):
            self.is_moving = False
            return
        self.is_moving = True
        self.angle = pymunk.Vec2d((dx, dy)).angle
        self.physicser.apply_impulse(
            (dx * self.impulse_factor, dy * self.impulse_factor))

    def set_position(self, position):
        self.physicser.position = position

    def copy_state(self, old_protagonist):
        self.physicser.position = old_protagonist.physicser.position
        self.physicser.switch_form(self.form, old_protagonist.form)
        self.impulse_factor = old_protagonist.impulse_factor
        self.form = old_protagonist.form
        self.angle = old_protagonist.angle
        self.inventory = old_protagonist.inventory

    def toggle_form(self):
        if self.form == self.WOLF_FORM:
            self.go_human()
        else:
            self.go_werewolf()

    def get_current_interactible(self):
        for shape in self.get_space().shape_query(self.get_shape()):
            if shape.collision_type in NON_GAME_OBJECT_COLLIDERS:
                # No game object here.
                continue
            interactible = shape.physicser.game_object.interactible
            if interactible is not None:
                return interactible
        return None

    def perform_action(self):
        """Perform an action on the target.
        """
        interactible = self.get_current_interactible()
        if interactible is None:
            # Nothing to interact with.
            return
        action = interactible.select_action(self)
        if action is None:
            # Nothing to do with it.
            return
        return action.perform(self)

    def attack(self):
        """Attempt to hurt something.
        """
        if self.in_wolf_form():
            self.claw()
        else:
            self.shoot()

    def shoot(self):
        if not self.has_item('gun'):
            return
        vec = Vec2d.unit()
        vec.angle = self.angle
        vec.length = 1000
        FireEvent.post(self.physicser.position, vec, 10, COLLISION_TYPE_PLAYER)

    def claw(self):
        vec = Vec2d.unit()
        vec.angle = self.angle
        vec.length = 100
        ClawEvent.post(self.physicser.position, vec,
                       COLLISION_TYPE_WEREWOLF_ATTACK)
        print "Claw", self.physicser.position, vec

    def in_wolf_form(self):
        return self.form == self.WOLF_FORM

    def in_human_form(self):
        return self.form == self.HUMAN_FORM

    def has_item(self, item):
        return item in self.inventory

    def environmental_movement(self, dx, dy):
        if (dx, dy) == (0, 0):
            return
        self.physicser.apply_impulse((dx, dy))

    def get_health_level(self):
        """Return current health level
        """
        return self.health_level

    def die(self):
        # Handle player death - may be called due to other reasons
        # than zero health
        ScreenChange.post('dead')

    def lose_health(self, amount):
        if self.in_human_form():
            self.health_level -= amount
        else:
            self.health_level -= amount / WEREWOLF_SOAK_FACTOR
        if self.health_level <= PROTAGONIST_HEALTH_MIN_LEVEL:
            self.die()

    def gain_health(self, amount):
        self.health_level += amount
        if self.health_level > PROTAGONIST_HEALTH_MAX_LEVEL:
            self.health_level = PROTAGONIST_HEALTH_MAX_LEVEL

    def update(self, dt):
        last_secs = int(self._time)
        self._time += dt
        secs = int(self._time)
        if self.form == self.WOLF_FORM and secs > last_secs:
            self.gain_health(1)
        super(Protagonist, self).update(dt)