view nagslang/protagonist.py @ 217:d98daba73055

Composition-based renderers.
author Jeremy Thurgood <firxen@gmail.com>
date Wed, 04 Sep 2013 14:53:37 +0200
parents 325c317cbfa1
children 9e2ef2f15035
line wrap: on
line source

import pymunk
import pymunk.pygame_util

import math

from nagslang import render
from nagslang.constants import COLLISION_TYPE_PLAYER, ZORDER_MID
from nagslang.game_object import GameObject, Physicser, make_body
from nagslang.mutators import FLIP_H
from nagslang.resources import resources


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 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'
    HUMAN_FORM_BACK = 'human_back'
    WOLF_FORM = 'wolf'
    WOLF_FORM_BACK = 'wolf_back'

    def __init__(self, space, position):
        physicser = self._make_physics(space, position)
        self._setup_renderers()
        self.inventory = {}
        self.form = self.HUMAN_FORM
        self.render_form = self.HUMAN_FORM

        super(Protagonist, self).__init__(
            physicser, self._renderers[self.form])
        self.zorder = ZORDER_MID

        self.go_human()

    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 _setup_renderers(self):
        self.angle = 0
        self._renderers = {
            self.HUMAN_FORM: render.FacingSelectionRenderer(
                {
                    'left': render.MovementAnimatedRenderer(
                        [self._get_image('human_1.png'),
                         self._get_image('human_2.png')], 3),
                    'right': render.MovementAnimatedRenderer(
                        [self._get_image('human_1.png', FLIP_H),
                         self._get_image('human_2.png', FLIP_H)], 3),
                }),
            self.HUMAN_FORM_BACK: render.FacingSelectionRenderer(
                {
                    'left': render.MovementAnimatedRenderer(
                        [self._get_image('human_back_1.png'),
                         self._get_image('human_back_2.png')], 3),
                    'right': render.MovementAnimatedRenderer(
                        [self._get_image('human_back_1.png', FLIP_H),
                         self._get_image('human_back_2.png', FLIP_H)], 3),
                }),
            self.WOLF_FORM: render.FacingSelectionRenderer(
                {
                    'left': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_1.png'),
                         self._get_image('werewolf_2.png')], 3),
                    'right': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_1.png', FLIP_H),
                         self._get_image('werewolf_2.png', FLIP_H)], 3),
                }),
            self.WOLF_FORM_BACK: render.FacingSelectionRenderer(
                {
                    'left': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_back_1.png'),
                         self._get_image('werewolf_back_2.png')], 3),
                    'right': render.MovementAnimatedRenderer(
                        [self._get_image('werewolf_back_1.png', FLIP_H),
                         self._get_image('werewolf_back_2.png', FLIP_H)], 3),
                }),
        }
        for renderer in self._renderers.values():
            renderer.set_game_object(self)

    @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):
        return self.angle

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

        if self.render_form == self.HUMAN_FORM:
            self.render_form = self.WOLF_FORM
        elif self.render_form == self.HUMAN_FORM_BACK:
            self.render_form = self.WOLF_FORM_BACK
        else:
            self.render_form = self.WOLF_FORM
        self.renderer = self._renderers[self.render_form]

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

        if self.render_form == self.WOLF_FORM:
            self.render_form = self.HUMAN_FORM
        elif self.render_form == self.WOLF_FORM_BACK:
            self.render_form = self.HUMAN_FORM_BACK
        else:
            self.render_form = self.HUMAN_FORM
        self.renderer = self._renderers[self.render_form]

    def _switch_to_back(self):
        if self.render_form == self.HUMAN_FORM:
            self.render_form = self.HUMAN_FORM_BACK
        elif self.render_form == self.WOLF_FORM:
            self.render_form = self.WOLF_FORM_BACK
        self.renderer = self._renderers[self.render_form]

    def _switch_to_front(self):
        if self.render_form == self.HUMAN_FORM_BACK:
            self.render_form = self.HUMAN_FORM
        elif self.render_form == self.WOLF_FORM_BACK:
            self.render_form = self.WOLF_FORM
        self.renderer = self._renderers[self.render_form]

    def set_direction(self, dx, dy):
        if (dx, dy) == (0, 0):
            return
        old_angle = self.angle
        self.angle = pymunk.Vec2d((dx, dy)).angle
        # If we've gone from quadrants 2 & 3 to 1 & 4 (or vice versa)
        # switch between front & back views
        if self.angle != math.pi:
            # == math.pi is going straight left, which can't
            # trigger a front/back swap and simplifies these checks
            if self.angle > 0 and old_angle != self.angle:
                self._switch_to_back()
            elif self.angle < 0 and old_angle != self.angle:
                self._switch_to_front()
        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.render_form = old_protagonist.render_form
        self.inventory = old_protagonist.inventory
        self.renderer = self._renderers[self.render_form]

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

    def act_on(self, target):
        """Perform an action on the target.
        """
        # TODO: Decide how best to do this.
        pass

    def attack(self):
        """Attempt to hurt something.
        """
        pass

    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))