Source

hexbattle / hexbattle_overlays.py

Full commit
#!/usr/bin/env python
# encoding: utf-8

"""Overlay classes for hexbattle scenes."""

# standard imports
from os.path import join
from random import choice
from time import time, sleep

# pyglet imports
from pyglet import image, resource, gl
from pyglet.text import Label

# own stuff
# hex functions
from hexgrid import *
from hexbattle_hex import *
from core import Sprite


class Action(Hexfield):
    """A simple action for the command overlay"""
    def __init__(self, scene, action=None, *args, **kwds):
        if action is None:
            image_path=join("graphics", "hex-overlay.png")
        else:
            image_path=join(action["image"])
        super(Action, self).__init__(scene, image_path=image_path,
                                     *args, **kwds)
        self.action = action
        
    def clicked(self):
        """The action was clicked: get active and return the next step.

        If we return None, the command wheel is finished."""
        return self.action


class TextBackground(Hexfield): 
    """The background hexes for text overlays."""
    def __init__(self, scene, *args, **kwds):
        image_path=join("graphics", "hex-textbackground.png")
        super(TextBackground, self).__init__(scene, image_path=image_path,
                                     *args, **kwds)

class CharacterImage(Hexfield): 
    """The background hexes for text overlays."""
    def __init__(self, scene, img=None, char=None, *args, **kwds):
        image_path=img
        super(CharacterImage, self).__init__(scene, image_path=image_path,
                                     *args, **kwds)        

class Overlay(object):
    @property
    def visible(self):
        return self.fields.values()

class CommandOverlay(Overlay):
    """An overlay which prompts the user to click actions.

    >>> # Use the overlay without scene — nonfunctional ⇒ only for testing. 
    >>> c = CommandOverlay(None)

    TODO: Maybe change the overlay to allow for a hidden, yet already created overlays. Currently self.show creates a new overlay at the given position."""
    def __init__(self, scene):
        self.scene = scene
        self.hex_x = 0
        self.hex_y = 0
        #: The fields on which we want to be.
        self.fields = {}
        #: The hexes we want to place stuff on, relative to our position.
        self.hex_vectors = hex_vectors

    @property
    def is_visible(self):
        return self.fields

    def _prepare_actions(self, actions):
        """Setup the list of actions"""
        # hide and clear the current actions.
        self.hide()
        # and get new ones.
        if actions is None:
            # TODO: Get the default list of actions.
            actions = [None for i in range(7)]
        else:
            actions = actions[:]
            # first object is cancel.
            actions.insert(0, None)
            # undefined actions are None
            actions = actions + [None for i in range(7-len(actions))]
        for i in range(len(actions)):
            hex_x, hex_y = self.hex_vectors[i]
            hex_x += self.hex_x
            hex_y += self.hex_y
            x, y = hexgrid_to_coordinate(hex_x, hex_y)
            action = Action(self.scene, action=actions[i], x = x, y = y)
            self.scene.hexmap_overlay[(hex_x, hex_y)] = action
            self.fields[(hex_x, hex_y)] = action

    def draw(self):
        for action in self.fields.values():
            action.draw()
    
    def show(self, hex_x, hex_y, actions=None):
        """Create and show the command overlay at the given position. If it gets actions, use these."""
        self.hex_x = hex_x
        self.hex_y = hex_y
        cmds = self._prepare_actions(actions)

    def hide(self):
        """Disappear and let all actions die."""
        for pos in self.fields:
            try: del self.scene.hexmap_overlay[pos]
            except KeyError: print "no such item in overlay hexmap", pos
        self.fields = {}

    def on_mouse_release(self, x, y, buttons=None, modifiers=None):
        """Process a mouse event."""
        hex_pos = coordinate_to_hexgrid(x, y)
        # TODO: Tell the model, that we tried to move here.
        actor = self.fields.get(hex_pos, None)
        if actor is not None:
            action = actor.clicked()
            return action


class MovementOverlay(Overlay):
    """An overlay to show where a unit can move.

    >>> # Use the overlay without scene — nonfunctional ⇒ only for testing. 
    >>> c = CommandOverlay(None)

    TODO: Maybe change the overlay to allow for a hidden, yet already created overlays. Currently self.show creates a new overlay at the given position.
    """
    def __init__(self, scene):
        self.scene = scene
        self.hex_x = 0
        self.hex_y = 0
        #: The fields on which we want to be.
        self.fields = {}
        #: The hexes we want to place stuff on, relative to our position.

    @property
    def is_visible(self):
        return self.fields

    def show(self, positions):
        for pos in positions:
            x, y = hexgrid_to_coordinate(*pos)
            a = Action(self.scene, x=x, y=y)
            a.move_to_hex(*pos)
            self.fields[pos] = a
            self.scene.hexmap_overlay[pos] = a
            
    def draw(self):
        for action in self.fields.values():
            action.draw()
    
    def hide(self):
        """Disappear and let all actions die."""
        for pos in self.fields:
            try:
                if self.scene.hexmap_overlay[pos] is self.fields[pos]: 
                    del self.scene.hexmap_overlay[pos]
            except KeyError: print "no such item in overlay hexmap", pos
        self.fields = {}

    def on_mouse_release(self, x, y, buttons=None, modifiers=None):
        """Process a mouse event."""
        hex_pos = coordinate_to_hexgrid(x, y)
        act = self.fields.get(hex_pos, None)
        if act is not None:
            action = act.clicked()
            return action
    


class TextOverlay(Overlay):
    """An overlay to show Text.

    >>> # Use the overlay without scene — nonfunctional ⇒ only for testing. 
    >>> c = TextOverlay(None)
    """
    def __init__(self, scene, text=None):
        self.scene = scene
        self.hex_x = 0
        self.hex_y = 0
        self.text = text
        #: The fields on which we want to be.
        self.fields = {}
        #: The hexes we want to place stuff on, relative to our position.

    @property
    def visible(self):
        return self.fields.values() + [self.text]

    @property
    def is_visible(self):
        return self.fields

    def _squareedges(self, pos0, pos1): 
        """Get the lower left and upper right hexes of a square"""
        if pos0[0] < pos1[0]: 
            left, right = pos0, pos1
        else: 
            left, right = pos1, pos0
        if left[1] + 0.5*left[0] < right[1] + 0.5*right[0]: 
            lowerleft = left
            upperright = right
        else: 
            lowerleft = left[0], right[1]
            upperright = right[0], left[1]
        return lowerleft, upperright

    def squarehexes(self, pos0, pos1): 
        hexes = []
        lowerleft, upperright = self._squareedges(pos0, pos1)
        # the +1 is needed to get the right x. I don’t know why. TODO: Fix cleanly.
        for x in range(lowerleft[0], upperright[0]+1): 
            dxl = x - lowerleft[0]
            dxr = upperright[0] - x
            righty = upperright[1] + int(0.5*dxr+0.1)
            lefty = lowerleft[1] - int(0.5*dxl+0.1)
            maxy = max(righty, lefty) + dxr%2
            # if the righthex is odd, we need to add padding on top for lower hexes
            miny = min(righty, lefty) - dxl%2
            for y in range(miny, maxy+1): 
                hexes.append((x, y))
        return hexes

    def show(self, text, x=None, y=None):
        self.hide()
        self.text = Label(text, width=self.scene.core.win.width, multiline=True)
        width, height = self.text.content_width, self.text.content_height
        # make sure the layout takes the text height as height
        self.text.height = height
        if x is None: 
            x = self.scene.core.win.width / 2.0 - width / 2.0
        if y is None: 
            y = self.scene.core.win.height / 2.0 + height / 2.0
        # slightly shifted: box around the text
        x, y = x+18, y+18
        self.text.x, self.text.y = x, y
        self.text.color = (0, 0, 0, 255)
        minpos = coordinate_to_hexgrid(x - 18, y - height)
        # +36 to make sure that it gives a border around the text
        maxpos = coordinate_to_hexgrid(x + width + 18, y)
        positions = self.squarehexes(minpos, maxpos)

        for pos in positions:
            x, y = hexgrid_to_coordinate(*pos)
            a = TextBackground(self.scene, x=x, y=y)
            a.move_to_hex(*pos)
            self.fields[pos] = a
            
    def draw(self):
        for field in self.fields.values():
            field.draw()
        self.text.draw()
    
    def hide(self):
        """Disappear and let all actions die."""
        self.fields = {}
        self.text = Label("")


class TheaterOverlay(TextOverlay):
    """An overlay to show Character Images.

    >>> # Use the overlay without scene — nonfunctional ⇒ only for testing. 
    >>> c = TextOverlay(None)
    """
    def __init__(self, scene, leftimage=None, rightimage=None):
        self.scene = scene
        self.hex_x = 0
        self.hex_y = 0
        self.leftimage = leftimage
        self.rightimage = rightimage
        #: The fields on which we want to be.
        self.fields = {}
        #: The hexes we want to place stuff on, relative to our position.

    @property
    def visible(self):
        return self.fields.values() + [im for im in (
                self.leftimage, self.rightimage) if im]

    @property
    def is_visible(self):
        return self.fields or self.leftimage or self.rightimage

    def show(self, image, position=None):
        winwidth = self.scene.core.win.width
        winheight = self.scene.core.win.height

        if position=="right" or self.leftimage:
            self.rightimage = CharacterImage(self.scene, image)
            # mirror the image
            self.rightimage.image = self.rightimage.image.get_texture().get_transform(flip_x=True)
            # move it to the right side
            self.rightimage.x = winwidth
            if not position: position = "right"
        else:
            self.leftimage = CharacterImage(self.scene, image)
            if not position: position = "left"

        if self.leftimage:
            width,height = self.leftimage.width, self.leftimage.height
            leftmin = coordinate_to_hexgrid(0, 0)
            # +36 to make sure that it gives a border around the text
            leftmax = coordinate_to_hexgrid(width,height)
            leftpositions = self.squarehexes(leftmin, leftmax)
        else: leftpositions = []

        if self.rightimage:
            width,height = self.rightimage.width, self.rightimage.height
            rightmin = coordinate_to_hexgrid(winwidth - width, winheight - height)
            rightmax = coordinate_to_hexgrid(winwidth, winheight)
            rightpositions = self.squarehexes(rightmin, rightmax)
        else: rightpositions = []
        
        for pos in []:#leftpositions+rightpositions:
            x, y = hexgrid_to_coordinate(*pos)
            a = TextBackground(self.scene, x=x, y=y)
            a.move_to_hex(*pos)
            self.fields[pos] = a
            
    def draw(self):
        for field in self.fields.values():
            field.draw()
        if self.leftimage:
            self.leftimage.draw()
        if self.rightimage:
            self.rightimage.draw()

    def hide(self, position=None):
        """Let one or all chars disappear."""
        if not position:
            self.fields = {}
            self.text = Label("")
            self.leftimage, self.rightimage = None, None
        elif position == "left":
            self.leftimage = None
        elif position == "right":
            self.rightimage = None



if __name__ == "__main__":
    # there’s only one reason for calling this file: doctests
    from doctest import testmod
    testmod()