Exemple #1
0
def load_entities(charnames):
    wds = list()
    skins = list()

    for charname in charnames:
        path = os.path.join(usf_root, 'data', 'characters', charname)
        path2 = os.path.join(usf_root, 'data', 'characters_unfinished', charname)

        if os.path.exists(path):
            if not os.path.exists(
                os.path.join(path,path.split(os.path.sep)[-1])+".xml"
                ):
                create_character_xml(path)

            skins.append(EntitySkin(path))

            if inotifyx:
                wds.append(inotifyx.add_watch(fd, path, inotifyx.IN_MODIFY))

        elif os.path.exists(path2):
            if not os.path.exists(
                os.path.join(path2,path2.split(os.path.sep)[-1])+".xml"
                ):
                create_character_xml(path2)
            skins.append(EntitySkin(path2))

            if inotifyx:
                wds.append(inotifyx.add_watch(fd, path2, inotifyx.IN_MODIFY))

        else:
            logging.error("no directory of this name in characters.")

    return skins, wds
    def __init__(self, **kwargs):
        super(Entity, self).__init__(**kwargs)

        number = kwargs.get('num')

        if number:
            self._num = number

        else:
            self._num = Entity.counter
            Entity.counter += 1

        self._game = kwargs.get('game')
        self._upgraded = kwargs.get('upgraded', False)
        self._lighten = False
        self._shield = {'on': False, 'power': 1.0, 'date': 0}
        self._carried_by = kwargs.get('carried_by', None)
        # the entity is reversed when looking left.
        self._percents = 0
        self._lives = kwargs.get('lives', 3)
        self._invincible = False
        self._visible = kwargs.get('visible', False)

        entity_skinname = kwargs.get(
                'entity_skinname',
                'characters' + os.sep + 'stick')

        if entity_skinname is not None:
            animation = kwargs.get('animation', 'static')
            self._name = entity_skinname.split(os.sep)[-1]
            self.entity_skin = EntitySkin(
                    entity_skinname,
                    not self._game or not self._game.screen,
                    animation=animation)

            self._armor = self.entity_skin.armor
            self._rect = pygame.Rect(0, 0, 0, 0)
            self._rect[:2] = (
                    self._place[0] - self._rect[2] / 2,
                    self._place[1] - self._rect[3])

            self._rect[2:] = self.entity_skin.animation.rect[2:]
            self.entity_skin.update(0,
                    server=(self._game is None or self._game.screen is None))

            self._game.events.add_event(
                    'ShieldUpdateEvent',
                    (None, None),
                    {'world': self._game, 'player': self})
class Entity(Actor):
    """
    Provide an entity object, which will take care of lives, movements,
    collisions of an Entity. Players and Items are Entities.

    This is a big class, and it uses a few counterintuitive concepts. First,
    its vectors are defined relatively, that means moving "forward/backward"
    instead of moving "left/right." Also, the vector representing the walking
    movement of the entity is seperated from the main vector.

    """

    # Precalculation of some sin-cos list to speed up collision detection.
    # number of points cannot be changed currently due to not adaptative
    # collision method.
    nb_points = 8
    ai = False
    ai_ = None
    list_sin_cos = [[
                math.sin(i * math.pi / (nb_points / 2) + math.pi / nb_points),
                math.cos(i * math.pi / (nb_points / 2) + math.pi / nb_points)]
            for i in range(nb_points)]

    list_sin_cos_1 = map(lambda (x, y): (x + 1, y + 1), list_sin_cos)

    # this counter will allow us to correctly update entities.
    counter = 0

    def __init__(self, **kwargs):
        super(Entity, self).__init__(**kwargs)

        number = kwargs.get('num')

        if number:
            self._num = number

        else:
            self._num = Entity.counter
            Entity.counter += 1

        self._game = kwargs.get('game')
        self._upgraded = kwargs.get('upgraded', False)
        self._lighten = False
        self._shield = {'on': False, 'power': 1.0, 'date': 0}
        self._carried_by = kwargs.get('carried_by', None)
        # the entity is reversed when looking left.
        self._percents = 0
        self._lives = kwargs.get('lives', 3)
        self._invincible = False
        self._visible = kwargs.get('visible', False)

        entity_skinname = kwargs.get(
                'entity_skinname',
                'characters' + os.sep + 'stick')

        if entity_skinname is not None:
            animation = kwargs.get('animation', 'static')
            self._name = entity_skinname.split(os.sep)[-1]
            self.entity_skin = EntitySkin(
                    entity_skinname,
                    not self._game or not self._game.screen,
                    animation=animation)

            self._armor = self.entity_skin.armor
            self._rect = pygame.Rect(0, 0, 0, 0)
            self._rect[:2] = (
                    self._place[0] - self._rect[2] / 2,
                    self._place[1] - self._rect[3])

            self._rect[2:] = self.entity_skin.animation.rect[2:]
            self.entity_skin.update(0,
                    server=(self._game is None or self._game.screen is None))

            self._game.events.add_event(
                    'ShieldUpdateEvent',
                    (None, None),
                    {'world': self._game, 'player': self})

    @property
    def num(self):
        """
        return entity number
        """
        return self._num

    @property
    def hardshape(self):
        """
        return current hardshape from entity_skin
        """
        return self.entity_skin.hardshape

    @property
    def name(self):
        """
        name of the entity
        """
        return self._name

    @property
    def lives(self):
        """
        return current number of lives of the entity
        """
        return self._lives

    def set_lives(self, value):
        """
        change current number of lives of the entity
        """

        assert isinstance(value, int)
        self._lives = value

    @property
    def armor(self):
        """ return current armor state
        """
        return self._armor

    @property
    def percents(self):
        """
        return current percents
        """
        return self._percents

    def set_percents(self, value):
        """
        change percents value
        """
        self._percents = value

    def add_percents(self, value):
        """
        increment the current player percents
        """
        self._percents += value
        self._percents = max(self.percents, 0)

    @property
    def shield(self):
        """
        return current state of the shield
        """
        return self._shield

    @property
    def upgraded(self):
        """
        return True if the player is currently upgraded
        """
        return self._upgraded

    def set_upgraded(self, value):
        """
        Set the upgraded state of the player, True or False
        """
        assert value in (True, False)
        self._upgraded = value

    @property
    def visible(self):
        """
        set if the entity is currently visible on screen
        """
        return self._visible

    def set_visible(self, value):
        """
        Set the visibility statue of the entity
        """
        self._visible = value

    @property
    def invincible(self):
        """
        status of the entity vulnerability to hits
        """
        return self._invincible

    def set_invincible(self, value):
        """
        set the entity invulnerability status
        """
        assert value in (True, False)
        self._invincible = value

    @property
    def lighten(self):
        """
        True if the entity should be currently displayed lightened
        """
        return self._lighten

    def set_lighten(self, value):
        """
        Set lighten attribute of entity to value
        """
        assert value in (True, False)
        self._lighten = value

    def backup(self):
        """
        save important attributes of the state of the player, to a dict
        """
        assert isinstance(self._vector, list)
        d = {
            '_lives': self.lives,
            '_place': self.place[:],
            '_rect': pygame.Rect(self.rect[:]),
            '_vector': self._vector[:],
            '_walking_vector': self.walking_vector[:]}

        # would be easier with a dict comprehension, but not yet in 2.6
        for k in ('_reversed', '_percents', '_upgraded', '_present',
                '_visible'):
            d[k] = self.__dict__[k]
        return d

    def restore(self, backup):
        """
        restore the game to the state described in backup
        """
        self.__dict__.update(backup)
        assert isinstance(self._vector, list)

    @property
    def agressiv_points(self):
        """
        return agressive points of the current frame
        """
        return self.entity_skin.animation.agressivpoints

    def test_hit(self, entity):
        """
        test entity aggressive points collisions with other entity
        """
        for point in self.agressiv_points:
            if entity.rect.colliderect(
                    self.rect[0] + point[0][0] - 3,
                    self.rect[1] + point[0][1] - 3,
                    6,
                    6):
                entity.hit(point, self.reversed)

    def hit(self, point, reverse):
        """
        enforce the effect of a collision with an aggressive point. the
        point is a list of x, y,dx, dy coords, and reverse is a flag indicating
        if the attacking entity is reversed (to apply projection vectors)
        """

        if self.shield['on']:
            self.shield['power'] -= (math.sqrt(
                point[1][0] ** 2 + point[1][1] ** 2)
                / CONFIG.general.SHIELD_SOLIDITY)

            self.shield['power'] = max(0, self.shield['power'])
            self._percents += (math.sqrt(point[1][0] ** 2 + point[1][1] ** 2)
                    / (30 * (100 - self.armor)) / 2)

        else:
            direction = reverse != self.reversed and -1 or 1

            self.set_vector([direction * point[1][0] * (1 + self._percents),
                          point[1][1] * (1 + self._percents)])

            self._percents += math.sqrt((
                point[1][0] ** 2 + point[1][1] ** 2) / (30 * (100 - self.armor)))

            self.entity_skin.change_animation(
                    'take',
                    self._game,
                    params={'entity': self})

    def alive(self):
        """
        True if the player still has lives left
        """
        return self.lives > 0

    def _draw_debug(self, coords, zoom, surface, debug_params):
        """
        This method just calls all specific debug draw methods
        """
        self._draw_debug_levelmap(surface, debug_params)
        self._draw_debug_hardshape(coords, zoom, surface, debug_params)
        self._draw_debug_footrect(coords, zoom, surface, debug_params)
        self._draw_debug_current_animation(coords, surface, debug_params)

    def _draw_debug_levelmap(self, surface, debug_params):
        """
        show the level map directly, useful to debugging
        """
        if debug_params["levelmap"]:
            draw_rect(
                    surface,
                    pygame.Rect(self.place[0] / 8, self.place[1] / 8, 2, 2),
                    pygame.Color('red'))

    def _draw_debug_hardshape(self, coords, zoom, surface, debug_params):
        """
        if hardshape debug is set, draw the hardshape of the player on the screen
        """
        if self.visible:
            if debug_params.get('hardshape', False):
                draw_rect(
                        surface,
                        pygame.Rect(
                            coords[0] + self.hardshape[0] * zoom,
                            coords[1] + self.hardshape[1] * zoom,
                            self.hardshape[2] * zoom,
                            self.hardshape[3] * zoom),
                        pygame.Color(255, 0, 0, 127))

                for n, i in enumerate(self.update_points()):
                    draw_rect(
                            surface,
                            pygame.Rect((
                                coords[0] + (i[0] - self.rect[0]) * zoom,
                                coords[1] + (i[1] - self.rect[1]) * zoom,
                                2, 2)),
                            n > 3 and pygame.Color('green') or
                            pygame.Color('blue'))

    def _draw_debug_footrect(self, coords, zoom, surface, debug_params):
        """
        if footrect debug is set, draw the footrect collision rect to the screen
        """
        if self.visible:
            if debug_params.get('footrect', False):
                r = self.foot_rect
                draw_rect(
                        surface,
                        pygame.Rect(
                            coords[0] + (r[0] - self.rect[0]) * zoom,
                            coords[1] + (r[1] - self.rect[1]) * zoom,
                            r[2] * zoom,
                            r[3] * zoom),
                        pygame.Color(255, 255, 0, 127))

    def _draw_debug_current_animation(self, coords, surface, debug_params):
        """
        if the current_animation debug is set, write the name of the current
            animation near of the character
        """
        if self.visible:
            if debug_params.get('current_animation', False):
                surface.blit(
                    loaders.text(self.entity_skin.current_animation,
                    fonts['mono']['25']),
                    (coords[0], coords[1] - self.entity_skin.animation.rect[3]))

    def draw(self, coords, zoom, surface, debug_params=dict()):
        """
        Draw the entity on the surface(i.e: the screen), applying coordinates
        offsets and zoom scaling as necessary, implementation depends on the
        definition of the global "SIZE", as a 2 elements list of in integers,
        containing respective height and width of the screen.

        coords is a tuple containing the current position of the camera, zoom is
        the current zoom of the camera.

        """
        # Draw a point on the map at the entity position.
        if not self.present:
            return

        if self.visible:
            if not self.reversed:
                place = (
                    self.rect[0] - self.hardshape[0],
                    self.rect[1] - self.hardshape[1])
            else:
                place = (
                    self.rect[0],
                    self.rect[1] - self.hardshape[1])

            real_coords = (
                    int(place[0] * zoom) + coords[0],
                    int(place[1] * zoom) + coords[1])

            self._draw_debug(real_coords, zoom, surface, debug_params)
            if self.entity_skin.animation.trails and self.old_pos:
                for i, (x, y) in enumerate(reversed(self.old_pos)):
                    img = self.entity_skin.animation.trails[
                            len(self.old_pos) - (i + 1)]

                    surface.blit(
                          loaders.image(
                              img,
                              reversed=self.reversed,
                              zoom=zoom)[0],
                          (
                              int(x * zoom) + coords[0] - (
                                  not self.reversed and self.hardshape[0] or 0),
                              int(y * zoom) + coords[1] - self.hardshape[1]))

            skin_image = loaders.image(
                    self.entity_skin.animation.image,
                    reversed=self.reversed,
                    lighten=self.lighten,
                    zoom=zoom)

            surface.blit(
                    skin_image[0],
                    real_coords)

            if self.shield['on']:
                image = loaders.image(
                        os.path.sep.join(
                            (CONFIG.system_path, 'misc', 'shield.png')),
                        zoom=zoom * self.shield['power'] * 3)

                shield_coords = (
                        coords[0] + int(
                            self.rect[0]
                            + self.entity_skin.shield_center[0]
                            - .5 * image[1][2]) * zoom,
                        coords[1] + int(
                            self.rect[1]
                            + self.entity_skin.shield_center[1]
                            - .5 * image[1][3]) * zoom)
                surface.blit(image[0], shield_coords)

    def update(self, deltatime, gametime, game):
        """
        Global function to update everything about entity, deltatime is the
        time ellapsed since the precedent frame, gametime is the time since
        beginning of the game
        """

        self.old_pos = [self.rect[:2], ] + self.old_pos
        if (not self.entity_skin.animation.trails or len(self.old_pos) >
                len(self.entity_skin.animation.trails)):
            self.old_pos.pop()

        if self.present:
            self.entity_skin.update(gametime, self.reversed, self.upgraded)
            self._update_physics(deltatime, game)

    def _update_rect(self):
        """
        update entity rect using position and harshape place/size, necessary
        when entity moved or animation frame changed.

        """
        h = self.hardshape
        self._rect = [
                self._place[0] - h[2] / 2 - h[0],
                self._place[1] - h[1],
                h[2],
                h[3]]

    def _foot_collision_rect(self):
        """
        return current foot collision rect
        """
        return pygame.Rect(
                self.rect[0] + self.hardshape[0],
                self.rect[1] + self.hardshape[3],
                self.hardshape[2],
                15)

    def point(self, n):
        """
        Return a collision point of the entity
        """
        h = self.hardshape
        r = self._rect
        # i think r[0] and r[1] should be used in these formulas, but they break
        # it, so maybe i'm wrong
        return (int(Entity.list_sin_cos_1[n][0] * h[2] / 2 + r[0]),
                int(Entity.list_sin_cos_1[n][1] * h[3] / 2 + r[1]))

    def update_points(self, x=0, y=0):
        """ Points are created as follows::

                +-------------------+
                |       7   0       |
                |   6           1   |
                |                   |
                |   5           2   |
                |       4   3       |
                +-------------------+

            As can be seen, they are oriented clock-wise, beginning in the
            upper-right corner and ending in the upper-left.
         """

        h = self.hardshape
        r = self._rect
        n = Entity.nb_points

        # reference version, non-optimized and should be easier to
        # understand
        #l = Entity.list_sin_cos
        #return [
        #        (
        #            int(l[i][0] * h[2] / 2 + h[2] / 2 + h[0] + r[0] + x),
        #            int(l[i][1] * h[3] / 2 + h[3] / 2 + h[1] + r[1] + y))
        #        for i in xrange(Entity.nb_points)]

        # optimised version
        h2_2 = h[2] / 2
        h3_2 = h[3] / 2
        hrx, hry = h[0] + r[0] + x, h[1] + r[1] + y
        l2 = Entity.list_sin_cos_1

        return [
                (
                    int(l2[i][0] * h2_2 + hrx),
                    int(l2[i][1] * h3_2 + hry))
                for i in xrange(n)]

    def collide_top(self, game):
        """
        if one of the two lower points collide, the entity bounces up and its
        horizontal speed is lowered
        """
        return (game.level.collide_rect(self.point(self.TOP_LEFT))
                or game.level.collide_rect(self.point(self.TOP_RIGHT)))

    def collide_bottom(self, game):
        """
        test of points and consequences on vectors if one of the two upper
        points collide, the entity bounces down.
        """
        return (game.level.collide_rect(self.point(self.BOTTOM_RIGHT))
                or game.level.collide_rect(self.point(self.BOTTOM_LEFT)))

    def collide_front(self, game):
        """
        if one of the two left points collide and the entity is not
        reversed or one of the two right points collide and the entity is
        reversed and the player is pushed forward.
        """

        return (self.reversed and (
            game.level.collide_rect(self.point(self.UPPER_RIGHT)) or
            game.level.collide_rect(self.point(self.LOWER_RIGHT))) or
            not self.reversed and (
                game.level.collide_rect(self.point(self.LOWER_LEFT))
                or game.level.collide_rect(self.point(self.UPPER_LEFT))))

    def collide_back(self, game):
        """
        if one of the two left points collide and the entity is reversed or one
        of the two right points collide and the entity is not reversed and the
        player bounce back.
        """

        return (not self.reversed and (
            game.level.collide_rect(self.point(self.UPPER_RIGHT)) or
            game.level.collide_rect(self.point(self.LOWER_RIGHT))) or
            self.reversed and (
                game.level.collide_rect(self.point(self.UPPER_LEFT)) or
                game.level.collide_rect(self.point(self.LOWER_LEFT))))

    def _world_collide(self, game):
        """
        This test collision of the entity with the map (game.level.map).

        Method:
        Generation of a contact test "circle" (only 8 points actualy).
        Then we test points of this circle and modify entity vector based on
        points that gets collided, moving the entity in the right direction to
        get out of each collision.

        """

        if not self.physic:
            if game.level.collide_rect(self.point(self.TOP_LEFT)):
                self.set_lives(0)

        # this test should optimise most situations.
        elif game.level.collide_rect(self.rect[:2], self.rect[2:]) != -1:
            self._on_ground = False

            if self.collide_top(game):
                self._on_ground = True
                self._vector[1] = -math.fabs(
                    self.vector[1] * CONFIG.general.BOUNCE)

                self._vector[0] /= 2
                while self.collide_top(game):
                    self.move((0, -1))

            elif self.collide_bottom(game):
                if self.vector[1] < 0:
                    self._vector[1] = int(
                            -self.vector[1] * CONFIG.general.BOUNCE)

                self._vector[0] /= 2
                while self.collide_bottom(game):
                    self.move((0, 1))

            if self.collide_front(game):
                self._vector[0] = math.fabs(self.vector[0]) / 2
                while self.collide_front(game):
                    self.move((2, 0))

            elif self.collide_back(game):
                self._vector[0] = -math.fabs(self.vector[0]) / 2
                while self.collide_back(game):
                    self.move((-2, 0))

    def _update_physics(self, deltatime, game):
        """
        This function applies current movemements and various environemental
        vectors to the entity, and calculates collisions.

        """
        # Move in walking direction.
        self.move((
                    self.walking_vector[0] * deltatime,
                    self.walking_vector[1] * deltatime))

        self.foot_rect = self._foot_collision_rect()
        #self._on_ground = game.level.collide_rect(
                #self.foot_rect[:2],
                #self.foot_rect[2:])

        # follow the floor if it's moving
        floor_vector = self._update_floor_vector(game.level.moving_blocs)

        # get environmental vector if we collide some vector-block
        environnement_vector = self._get_block_vector(
                game.level.vector_blocs)

        environnement_friction = self._get_env_collision(
                game.level.water_blocs)

        self._vector = [
                self.vector[0] + environnement_vector[0],
                self.vector[1] + environnement_vector[1]]

        self._place = [
                self.place[0] + floor_vector[0],
                self.place[1] + floor_vector[1]]

        # Gravity
        if self.gravity and self.physic and not self.on_ground:
            self._vector[1] += float(CONFIG.general.GRAVITY) * deltatime

        elif not self.physic:
            #FIXME : it is a bit hackish
            self._vector[1] += -0.00001

        # Application of air friction.
        f = CONFIG.general.AIR_FRICTION * environnement_friction

        if self.physic:  # FIXME: and not a bullet
            self._vector[0] -= (f * self.vector[0] * deltatime)
            self._vector[1] -= (f * self.vector[1] * deltatime)

        # apply the vector to entity.
        self.move((self.vector[0] * deltatime, self.vector[1] * deltatime))

        if not self.physics:
            return

        # Avoid collisions with the map
        self._world_collide(game)

    @property
    def rect(self):
        """
        return current player rect
        """
        return pygame.Rect(self._rect)

    def move(self, (x, y)):
        """
        move the entity relatively to his referencial (if he looks left, moving
        positively on x means going left).

        """
        if self._reversed:
            x = -x

        self.set_place((self._place[0] + x, self._place[1] + y))

        self._update_rect()