changeset 300:da53bc29b94a

Add the game interface.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 10 Mar 2012 17:47:03 +0100
parents e04e402e6380
children 6f1ca1cb5238
files eosd pytouhou/formats/anm0.py pytouhou/game/enemy.py pytouhou/game/game.py pytouhou/game/text.py pytouhou/games/eosd.py pytouhou/ui/gamerunner.py
diffstat 7 files changed, 178 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/eosd
+++ b/eosd
@@ -61,6 +61,7 @@ def main(path, stage_num, rank, characte
 pathsep = os.path.pathsep
 default_data = (pathsep.join(('CM.DAT', 'th06*_CM.DAT', '*CM.DAT', '*cm.dat')),
                 pathsep.join(('ST.DAT', 'th6*ST.DAT', '*ST.DAT', '*st.dat')),
+                pathsep.join(('IN.DAT', 'th6*IN.DAT', '*IN.DAT', '*in.dat')),
                 pathsep.join(('102h.exe', '102*.exe', '東方紅魔郷.exe', '*.exe')))
 
 
--- a/pytouhou/formats/anm0.py
+++ b/pytouhou/formats/anm0.py
@@ -80,12 +80,12 @@ class ANM0(object):
     @classmethod
     def read(cls, file):
         nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12))
-        width, height, format, zero2 = unpack('<IIII', file.read(16))
+        width, height, format, unknown1 = unpack('<IIII', file.read(16))
         first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12))
-        version, unknown1, thtxoffset, hasdata, nextoffset, zero3 = unpack('<IIIIII', file.read(24))
+        version, unknown2, thtxoffset, hasdata, nextoffset, zero2 = unpack('<IIIIII', file.read(24))
         if version != 0:
             raise Exception #TODO
-        if (zero1, zero2, zero3) != (0, 0, 0):
+        if (zero1, zero2) != (0, 0):
             raise Exception #TODO
 
         sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)]
--- a/pytouhou/game/enemy.py
+++ b/pytouhou/game/enemy.py
@@ -319,7 +319,8 @@ class Enemy(object):
 
         # Adjust damages
         damages = min(70, damages)
-        score = (damages // 5) * 10 #TODO: give to which player?
+        score = (damages // 5) * 10
+        self._game.players[0].state.score += score #TODO: better distribution amongst the players.
 
         if self._game.spellcard:
             #TODO: there is a division by 3, somewhere... where is it?
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -29,7 +29,7 @@ from pytouhou.game.effect import Particl
 class Game(object):
     def __init__(self, resource_loader, players, stage, rank, difficulty,
                  bullet_types, laser_types, item_types,
-                 nb_bullets_max=None, width=384, height=448, prng=None):
+                 nb_bullets_max=None, width=384, height=448, prng=None, interface=None):
         self.resource_loader = resource_loader
 
         self.width, self.height = width, height
@@ -48,6 +48,7 @@ class Game(object):
         self.players_bullets = []
         self.players_lasers = [None, None]
         self.items = []
+        self.interface = interface
 
         self.stage = stage
         self.rank = rank
@@ -187,7 +188,7 @@ class Game(object):
         self.update_bullets() # Pri 11
         for laser in self.lasers: #TODO: what priority is it?
             laser.update()
-        # Pri 12 is HUD
+        self.interface.update() # Pri 12
 
         # 4. Cleaning
         self.cleanup()
@@ -344,7 +345,7 @@ class Game(object):
         # Filter out-of-scren items
         items = []
         for item in self.items:
-            if item.y < 448:
+            if item.y < self.height:
                 items.append(item)
             else:
                 self.modify_difficulty(-3)
new file mode 100644
--- /dev/null
+++ b/pytouhou/game/text.py
@@ -0,0 +1,78 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## 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 copy import copy
+
+from pytouhou.game.sprite import Sprite
+from pytouhou.vm.anmrunner import ANMRunner
+
+
+class Glyph(object):
+    def __init__(self, sprite, pos):
+        self._sprite = sprite
+        self._removed = False
+
+        self.x, self.y = pos
+
+
+class Text(object):
+    def __init__(self, pos, text, front_wrapper, ascii_wrapper):
+        self._sprite = Sprite()
+        self._anmrunner = ANMRunner(front_wrapper, 22, self._sprite)
+        self._anmrunner.run_frame()
+        self._removed = False
+        self._changed = True
+
+        self.text = ''
+        self.glyphes = []
+
+        self.front_wrapper = front_wrapper
+        self.ascii_wrapper = ascii_wrapper
+
+        self.x, self.y = pos
+        self.set_text(text)
+
+
+    def objects(self):
+        return self.glyphes + [self]
+
+
+    def set_text(self, text):
+        if text == self.text:
+            return
+
+        if len(text) > len(self.glyphes):
+            ref_sprite = Sprite()
+            anm_runner = ANMRunner(self.ascii_wrapper, 0, ref_sprite)
+            anm_runner.run_frame()
+            ref_sprite.corner_relative_placement = True #TODO: perhaps not right, investigate.
+            self.glyphes.extend(Glyph(copy(ref_sprite), (self.x + 14*i, self.y))
+                for i in range(len(self.glyphes), len(text)))
+        elif len(text) < len(self.glyphes):
+            self.glyphes[:] = self.glyphes[:len(text)]
+
+        for glyph, character in zip(self.glyphes, text):
+            glyph._sprite.anm, glyph._sprite.texcoords = self.ascii_wrapper.get_sprite(ord(character) - 21)
+            glyph._sprite._changed = True
+
+        self.text = text
+        self._changed = True
+
+
+    def update(self):
+        if self._changed:
+            if self._anmrunner and not self._anmrunner.run_frame():
+                self._anmrunner = None
+            self._changed = False
+
--- a/pytouhou/games/eosd.py
+++ b/pytouhou/games/eosd.py
@@ -20,6 +20,8 @@ from pytouhou.game.lasertype import Lase
 from pytouhou.game.itemtype import ItemType
 from pytouhou.game.player import Player
 from pytouhou.game.orb import Orb
+from pytouhou.game.effect import Effect
+from pytouhou.game.text import Text
 
 from math import pi
 
@@ -81,9 +83,56 @@ class EoSDGame(Game):
         characters = resource_loader.get_eosd_characters()
         players = [EoSDPlayer(state, self, resource_loader, characters[state.character]) for state in player_states]
 
+        interface = EoSDInterface(player_states, resource_loader)
+
         Game.__init__(self, resource_loader, players, stage, rank, difficulty,
                       bullet_types, laser_types, item_types, nb_bullets_max,
-                      width, height, prng)
+                      width, height, prng, interface)
+
+
+
+class EoSDInterface(Game):
+    def __init__(self, states, resource_loader):
+        self.states = states
+        front = resource_loader.get_anm_wrapper(('front.anm',))
+        ascii_wrapper = resource_loader.get_anm_wrapper(('ascii.anm',))
+
+        self.width = 640
+        self.height = 480
+        self.game_pos = (32, 16)
+
+        self.items = ([Effect((0, 32 * i), 6, front) for i in range(15)] +
+                      [Effect((416 + 32 * i, 32 * j), 6, front) for i in range(7) for j in range(15)] +
+                      [Effect((32 + 32 * i, 0), 7, front) for i in range(12)] +
+                      [Effect((32 + 32 * i, 464), 8, front) for i in range(12)] +
+                      [Effect((0, 0), 5, front)] +
+                      [Effect((0, 0), i, front) for i in range(5) + range(9, 16)])
+        for item in self.items:
+            item._sprite.allow_dest_offset = True #XXX
+
+        self.labels = {
+            'highscore': Text((500, 58), '0', front, ascii_wrapper),
+            'score': Text((500, 82), '0', front, ascii_wrapper),
+            'player': Text((500, 122), 'star star', front, ascii_wrapper),
+            'bombs': Text((500, 146), 'star star', front, ascii_wrapper),
+            'power': Text((500, 186), '0', front, ascii_wrapper),
+            'graze': Text((500, 206), '0', front, ascii_wrapper),
+            'points': Text((500, 226), '0', front, ascii_wrapper),
+            'framerate': Text((512, 464), '', front, ascii_wrapper),
+            'debug?': Text((0, 464), '', front, ascii_wrapper),
+        }
+
+
+    def update(self):
+        for elem in self.items:
+            elem.update()
+
+        player_state = self.states[0]
+
+        self.labels['score'].set_text('%09d' % player_state.score)
+        self.labels['power'].set_text('%d' % player_state.power)
+        self.labels['graze'].set_text('%d' % player_state.graze)
+        self.labels['points'].set_text('%d' % player_state.points)
 
 
 
--- a/pytouhou/ui/gamerunner.py
+++ b/pytouhou/ui/gamerunner.py
@@ -14,14 +14,16 @@
 
 import pyglet
 import traceback
+from itertools import chain
 
-from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable,
-                       glHint, glEnableClientState, glViewport,
+from pyglet.gl import (glMatrixMode, glLoadIdentity, glEnable, glDisable,
+                       glHint, glEnableClientState, glViewport, glScissor,
                        gluPerspective, gluOrtho2D,
                        GL_MODELVIEW, GL_PROJECTION,
                        GL_TEXTURE_2D, GL_BLEND, GL_FOG,
                        GL_PERSPECTIVE_CORRECTION_HINT, GL_FOG_HINT, GL_NICEST,
-                       GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY)
+                       GL_COLOR_ARRAY, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY,
+                       GL_SCISSOR_TEST)
 
 from pytouhou.utils.helpers import get_logger
 
@@ -35,7 +37,7 @@ class GameRunner(pyglet.window.Window, G
     def __init__(self, resource_loader, game=None, background=None, replay=None):
         GameRenderer.__init__(self, resource_loader, game, background)
 
-        width, height = (game.width, game.height) if game else (None, None)
+        width, height = (game.interface.width, game.interface.height) if game else (None, None)
         pyglet.window.Window.__init__(self, width=width, height=height,
                                       caption='PyTouhou', resizable=False)
 
@@ -51,13 +53,12 @@ class GameRunner(pyglet.window.Window, G
             self.game.players[0].state.bombs = self.replay_level.bombs
             self.game.difficulty = self.replay_level.difficulty
 
-        self.fps_display = pyglet.clock.ClockDisplay()
+        self.clock = pyglet.clock.get_default()
 
 
     def start(self, width=None, height=None):
-        width = width or (self.game.width if self.game else 640)
-        height = height or (self.game.height if self.game else 480)
-
+        width = width or (self.game.interface.width if self.game else 640)
+        height = height or (self.game.interface.height if self.game else 480)
         if (width, height) != (self.width, self.height):
             self.set_size(width, height)
 
@@ -77,14 +78,11 @@ class GameRunner(pyglet.window.Window, G
             pyglet.clock.tick()
             self.dispatch_events()
             self.update()
-            self.on_draw()
+            self.render_game()
+            self.render_interface()
             self.flip()
 
 
-    def on_resize(self, width, height):
-        glViewport(0, 0, width, height)
-
-
     def _event_text_symbol(self, ev):
         # XXX: Ugly workaround to a pyglet bug on X11
         #TODO: fix that bug in pyglet
@@ -141,23 +139,46 @@ class GameRunner(pyglet.window.Window, G
                 self.game.run_iter(keystate)
 
 
-    def on_draw(self):
+    def render_game(self):
         # Switch to game projection
         #TODO: move that to GameRenderer?
+        x, y = self.game.interface.game_pos
+        glViewport(x, y, self.game.width, self.game.height)
+        glScissor(x, y, self.game.width, self.game.height)
+        glEnable(GL_SCISSOR_TEST)
         glMatrixMode(GL_PROJECTION)
         glLoadIdentity()
-        gluPerspective(30, float(self.width) / float(self.height),
+        gluPerspective(30, float(self.game.width) / float(self.game.height),
                        101010101./2010101., 101010101./10101.)
 
         GameRenderer.render(self)
 
-        # Get back to standard orthographic projection
+        glDisable(GL_SCISSOR_TEST)
+
+
+    def render_interface(self):
+        # Interface
+        interface = self.game.interface
+        interface.labels['framerate'].set_text('%.2ffps' % self.clock.get_fps())
+
         glMatrixMode(GL_PROJECTION)
         glLoadIdentity()
         glMatrixMode(GL_MODELVIEW)
         glLoadIdentity()
+        gluOrtho2D(0., float(self.width), float(self.height), 0.)
+        glViewport(0, 0, self.width, self.height)
 
-        #TODO: draw interface
-        gluOrtho2D(0., float(self.game.width), 0., float(self.game.height))
-        self.fps_display.draw()
+        items = [item for item in interface.items if item._anmrunner and item._anmrunner._running]
+        labels = interface.labels
+        if items:
+            # Force rendering of labels
+            self.render_elements(items)
+            self.render_elements(chain(*(label.objects()
+                                            for label in labels.itervalues())))
+        else:
+            self.render_elements(chain(*(label.objects()
+                                            for label in labels.itervalues()
+                                                if label._changed)))
+        for label in interface.labels.itervalues():
+            label._changed = False