1. Andrew
  2. pyShipCommand

Commits

Andrew  committed 1d2a978 Merge

Merge branch 'master' into script_tests

  • Participants
  • Parent commits d14aaae, 005e7da
  • Branches master

Comments (0)

Files changed (18)

File djlib/game.py

View file
+
+
+class GameClass:
+    
+    def __init__(self):
+        self.curr_state = None
+        self.running = False
+        self.known_states = {}
+
+    def initialize(self):
+        self.running = True
+
+    def update(self):
+        if self.curr_state:
+            self.curr_state.processInput()
+            self.curr_state.update()
+
+            self.curr_state.render()
+
+    def changeState(self, state_class, *args):
+        assert(state_class)
+
+        if self.curr_state:
+            # Don't switch to the same state
+            if state_class is self.curr_state.__class__:
+                return
+
+            # Leave the current state
+            self.curr_state.leave()
+
+        # look for state class in known states
+        new_state = self.known_states.get(state_class)
+        if not new_state:
+            # Instantiate class
+            new_state = state_class()
+            self.known_states[state_class] = new_state
+
+            # initialize new state
+            new_state.gc = self
+            new_state.initialize()
+
+        # officially switch to new state
+        self.curr_state = new_state
+
+        # Finally enter into the next state
+        self.curr_state.enter(*args)
+
+    def closeState(self, state_class):
+
+        # Look for state in known states
+        if state in self.known_states:
+            # shutdown and remove from list
+            state.shutdown()
+            self.known_states.remove(state)
+            return True
+        return False
+
+    def shutdown(self):
+        self.running = False
+
+        # Leave the current state we are in
+        if self.curr_state:
+            self.curr_state.leave()
+            self.curr_state = None
+
+        # Shutdown all known states
+        for state in self.known_states.itervalues():
+            state.shutdown()
+        state = {}
+
+    def getActiveState(self):
+        return self.curr_state
+        
+#end GameClass
+
+class GameState:
+
+    def __init__(self):
+        self.gc = None
+
+    def initialize(self):
+        """Called the first time the game is changed to this state
+           during the applications lifecycle."""
+
+    def enter(self):
+        """Called every time the game is switched to this state."""
+
+    def processInput(self):
+        """Called during normal update/render period for this state
+           to process it's input."""
+
+    def update(self):
+        """Called during normal update/render period for this state
+           to update it's local or game data."""
+
+    def render(self):
+        """Called during normal update/render period for this state
+           to render it's data in a specific way."""
+
+    def leave(self):
+        """Called whenever we switch from this state to another."""
+
+    def shutdown(self):
+        """Called during application shutdown."""
+
+    def changeState(self, state_class, *args):
+        """Shortcut for this state to initiate transfer to a new state."""
+        self.gc.changeState(state_class, *args)
+#end GameState
+

File djlib/logger.py

View file
+
+from logging import *
+
+stack_depth = 0
+
+# XXX TODO: Should this only be done during some kind of logging initialization?
+CALL = 5 # below DEBUG(10)
+addLevelName(CALL, "CALL")
+
+def add_log_args(arg_parser):
+	# TODO: Add argements for stream location and trace filters
+	arg_parser.add_argument('-v', '--verbose', action='count', default=0,
+                     		help='Change the verbosity of the logging, each "-v" increases the ammount of logging '
+                           		 'by default this program will log at level INFO ("-vvv"). '
+                        		 '(levels: ERROR, WARN, INFO, DEBUG)')
+
+def set_log_options(parsed_args, root_name):
+    root_log = getLogger(root_name)
+
+    log_lvl = min(5, parsed_args.verbose)
+    root_log.setLevel({0: INFO, #default
+                       1: ERROR,
+                       2: WARN,
+                       3: INFO,
+                       4: DEBUG,
+                       5: CALL
+                      }.get(log_lvl, 0))
+
+    # Until log stream options are added, we will just use the default stream
+    handler = StreamHandler()
+
+    # change formatter depending on log level.
+    # debugging needs more info than general output
+    format_str = '%(levelname)-6s %(message)s'
+    if log_lvl > 3:
+    	format_str = '%(asctime)10s.%(msecs)3d %(levelname)-6s %(name)-24s %(message)s'
+    	
+    handler.setFormatter(Formatter(format_str, '%c'))
+    root_log.addHandler(handler)
+
+def log_function(logger, level = CALL):
+	def wrap_func(func):
+		def logged(*args, **kwargs):
+			global stack_depth
+			logger.log(level, "%s[ENTRY] %s::%s%s", stack_depth*'  ', func.__module__, func.__name__, str(args))
+			stack_depth += 1
+			ret = func(*args, **kwargs)
+			stack_depth -= 1
+			logger.log(level, "%s[EXIT ] %s::%s(...) -> (%s)", stack_depth*'  ', func.__module__, func.__name__, str(ret))
+			return ret
+		return logged
+	return wrap_func
+
+def log_method(logger, level = CALL):
+	def wrap_method(method):
+		def logged(self, *args, **kwargs):
+			global stack_depth
+			logger.log(level, "%s[ENTRY] %s[%X].%s%s", stack_depth*'  ', self.__class__.__name__, id(self), method.__name__, str(args))
+			stack_depth += 1
+			ret = method(self, *args, **kwargs)
+			stack_depth -= 1
+			logger.log(level, "%s[EXIT ] %s[%X].%s(...)->{%s}", stack_depth*'  ', self.__class__.__name__, id(self), method.__name__, str(ret))
+			return ret
+		return logged
+	return wrap_method

File pyshipcommand/network.py

View file
 from djlib.primitives import Vector
 class NetVector(pb.Copyable, pb.RemoteCopy, Vector):
 
+    @classmethod
+    def fromVector(cls, vec):
+        return NetVector(*vec.args())
+
     # serialize
     def getStateToCopy(self):
         d = self.__dict__.copy()
     
     def __init__(self, ship):
         self.id = ship.id
-        self.pos = NetVector(*ship.pos.args())
+        self.pos = NetVector.fromVector(ship.pos)
         self.speed = ship.vel.length()
         self.rotation = -int(self.degrees(self.atan2(ship.heading.y, ship.heading.x)))
         self.end_rot = None
 
         self.server_step = None
         self.universe = None
+        self.ships = None
 
 #end NetInit
 
 class NetCelestialBody(pb.Copyable, pb.RemoteCopy):
 
     def __init__(self, body):
-        self.pos = NetVector(*body.pos.args())
+        self.pos = NetVector.fromVector(body.pos)
         self.radius = body.radius
         self.type = -1
         for i, t in enumerate([celestial.Sun, celestial.Planet, celestial.Asteroid]):

File pyshipcommand/players.py

View file
 from network import NetVector, NetShip, NetInit, NetScript, NetCelestialBody
 pb.setUnjellyableForClass(NetVector, NetVector)
 
-from logging import getLogger
+from djlib.logger import getLogger, log_method
 log = getLogger('pyshipcommand.players')
 
 class Registrar(pb.Avatar):
     def __init__(self, remote):
         self.remote = remote
 
+    @log_method(log)
     def perspective_registerAccount(self, name, password):
         pm = getUtility(IManager, "Player")
 
 
         sm = getUtility(IManager, "Ship")
         self.mship = sm.createMothership(self.name, empire_pos)
-		
+
+
+    @log_method(log)		
     def connect(self, mind):
 
         if self.isConnected():
         log.info('Player: "%s" has connected', self.name)
         self.remote = mind
 
-        # send ship update immediately
-        self.sendShips()
         return True
-            
+
+
+    @log_method(log)            
     def disconnect(self, mind):
         if self.remote == mind:
             self.remote = None
             return True
         return False
 
+
+    @log_method(log)
     def remotePrint(self, msg):
         log.info('Sending message to Player: "%s" - %s', self.name, msg)
         if self.remote:
             self.remote.callRemote("print", msg)
-            
+
+
+    @log_method(log)
     def sendShips(self):
 
         # player might be disconnected
 
         self.sendLog()
 
+
+    @log_method(log)
     def sendUniverse(self):
         if not self.isConnected():
             return
         net_bodies = [NetCelestialBody(b) for b in bodies]
         self.remote.callRemote("updateUniverse", net_bodies) 
 
+
+    @log_method(log)
     def sendLog(self):
 
         if not self.isConnected():
             self.remote.callRemote("log", ship_log)
         log.info("%s: Sending ship log of length %d to %s", self.name, len(ship_log), self)
 
+
+    @log_method(log)
     def showText(self, text):
         if not self.isConnected():
             return
         log.info("%s: Showing text to player (showText) - %s", self.name, text)
         self.remote.callRemote("showText", text)
 
+
+    @log_method(log)
     def getShips(self):
         sm = getUtility(IManager, "Ship")
         return sm.getPlayerShips(self.name)
 
+
+    @log_method(log)
     def isConnected(self):
         return self.remote is not None
         
     # REMOTE CALLS
     ###################################
 
+    @log_method(log)
     def perspective_info(self):
         self.remotePrint("%s(%d)" % (self.name, self.id))
 
+
+    @log_method(log)
     def perspective_giveScript(self, name, data):
         log.info("%s: Receiving script %s", self.name, name)
         sm = getUtility(IManager, "Script")
         sm.giveScript(self.name, name, data)
 
+
+    @log_method(log)
     def perspective_giveMessage(self, msg):
-        log.info("%s: Receiving message:", self.name, msg)
+        log.info("%s: Receiving message: %s", self.name, str(msg))
         sm = getUtility(IManager, "Ship")
         mship = sm.getMothership(self.name)
         if sm.sendMessage(None, mship.id, 0, msg):
             self.remotePrint("Message Success")
         else:
             self.remotePrint("Message Failed")
-        
-    def perspective_scriptShip(self, ship_id, script_name):
+
+
+    @log_method(log)
+    def perspective_scriptShip(self, ship_id, script_name, action):
         sm = getUtility(IManager, "Ship")
         scm = getUtility(IManager, "Script")
         ship = sm.getShip(ship_id)
-        if not ship or not ship.setActiveScript(script_name):
+        if not ship or not ship.setActiveScript(script_name, action == "reboot"):
             self.remotePrint("Ship ID or Script invalid")
-        
+
+
+    @log_method(log)
     def perspective_getClientInit(self):
         from server import SERVER_UPDATE_INT
 
         if scripts:
             cinit.scripts = [NetScript(s.name, s.version, s.lastHash) for s in scripts.itervalues()]
 
+        # Send Universe
         univ = getUtility(IManager, "Universe")
         bodies = univ.getAllStatic()
         cinit.universe = [NetCelestialBody(b) for b in bodies]
+
+        # Send Ships
+        ships = self.getShips()
+        cinit.ships = [NetShip(s) for s in ships]
         
         return cinit
 
+
+    @log_method(log)
     def perspective_giveChat(self, msg):
         # Prepend player name
         msg = "%s: %s" % (self.name, msg)
         # Don't mute ourselves so we can see chat history
         pm.broadcastMessage(None, msg)
 
+
+    @log_method(log)
     def perspective_getPlayers(self):
 
         pm = getUtility(IManager, "Player")
 
 class AdminPlayer(Player):
 
+    @log_method(log)
     def getShips(self):
         #AdminPlayers get sent all ships
         sm = getUtility(IManager, "Ship")
     # REMOTE CALLS
     ###################################
 
+    @log_method(log)
     def perspective_addShip(self, pos):
         sm = getUtility(IManager, "Ship")
         sid, s = sm.createShip(self.name, ShipAttributes())
         log.info("Added Ship[%s] @ %s", sid, pos)
         self.remotePrint(str(s))
 
+
+    @log_method(log)
     def perspective_moveShip(self, ship_id, target_pos):
         from target import TargetPosition
         sm = getUtility(IManager, "Ship")
         
         self.remotePrint("Moving Ship[%s] to %s" % (ship_id, target_pos))
         ship.setTarget(TargetPosition(target_pos))
-        
+
+
+    @log_method(log)
     def perspective_orbitShip(self, ship_id, target_pos, radius):
         from target import TargetPosition, TargetOrbit
         sm = getUtility(IManager, "Ship")
         
         self.remotePrint("Orbiting Ship[%s] @ %s" % (ship_id, target_pos))
         pos = TargetPosition(target_pos)
-        if int(target_pos.x) >= sm._INDEX_MOD:
-            t_ship = sm.getShip(int(target_pos.x))
-            if t_ship:
-                log.debug("Ship[%s] is orbiting Ship[%s]", ship_id, t_ship)
-                # XXX TODO FIXME: i am pretty sure that this should be 
-                # pos = t_ship
-                # as i am assuming the intent is to orbit the targeted ship and not
-                # the ship already selected
-                pos = ship
+        t_ship = sm.getShip(int(target_pos.x))
+        if int(target_pos.y) == 0 and t_ship:
+            # What are the odds that Y would be 0 and X also match a ship ID?
+            # Should probably changes this ;)
+            log.debug("Ship[%s] is orbiting Ship[%s]", ship_id, t_ship)
+            pos = t_ship
         target = TargetOrbit(pos, radius)
         ship.setTarget(target)
-        
+
+
+    @log_method(log)        
     def perspective_followShip(self, ship_id, target_id, dist):
         from target import TargetFollow
         sm = getUtility(IManager, "Ship")
         self.remotePrint("Ship[%d] now following Ship[%d] at %d" % (ship_id, target_id, dist))
         ship.setTarget(TargetFollow(target, dist))
 
+
+    @log_method(log)
     def perspective_attackShip(self, ship_id, target_id, dmg):
         from target import TargetOrbit
         sm = getUtility(IManager, "Ship")
 
         ship.shoot(target_id, dmg, 100000)
 
+
+    @log_method(log)
     def perspective_getPlayers(self):
         #Overidden for Admins to provide more information
         pm = getUtility(IManager, "Player")
 
         self.connectedPlayers = []
 
+
+    @log_method(log)
     def requestAvatar(self, avatarId, mind, *interfaces):
         assert pb.IPerspective in interfaces
 
         
         return pb.IPerspective, player, lambda p=self:p.disconnectAvatar(player, mind)
 
+
+    @log_method(log)
     def connectAvatar(self, player, mind):
         if player.connect(mind):
             self.broadcastMessage(player, "%s has joined the Universe" % (player.name))
             self.connectedPlayers.append(player)
 
 
+    @log_method(log)
     def disconnectAvatar(self, player, mind):
         if player.disconnect(mind):
             self.connectedPlayers.remove(player)
             self.broadcastMessage(player, "%s has left the Universe" % (player.name))
 
 
+    @log_method(log)
     def getPlayer(self, identifier):
         if isinstance(identifier, int):
             return self.playersId.get(identifier)
             return self.playersName.get(identifier)
         return None
 
+
+    @log_method(log)
     def createPlayer(self, name):
 
         # Check if player already exists
         return player
 
 
+    @log_method(log)
     def loadPlayers(self, player_file):
         log.info("Loading Players:")
         players = []
         
         for player in players:
             log.info("Loaded Player %s: %s", player.id, player.name)
-            
+
+
+    @log_method(log)
     def broadcastMessage(self, muted_player, msg):
         for p in self.connectedPlayers:
             if not p == muted_player:

File pyshipcommand/server.py

View file
 from ship import ShipMgr
 from djlib.utils import IManager, IGame, IdAllocator, IAllocator
 
-from logging import getLogger
+from djlib.logger import getLogger, log_method, add_log_args, set_log_options
 log = getLogger('pyshipcommand.server')
 
 from collections import namedtuple
         self.accounts = None
         self.tick = 0
 
+    @log_method(log)
     def initialize(self):
         log.info("Intializing Server")
 
 
         return True
 
+    @log_method(log)
     def update(self):
         # XXX TODO: Consider renaming this 'tick' as this is common in game terminology for this
         #           sort of function. it is a tick and a ticks function is to update so slightly
         self.ship_mgr.sendAllShips()
         self.tick += 1
         
+    @log_method(log)
     def loadCredentials(self, checker):
 
         count = 0
 
         return count
 
+    @log_method(log)
     def addAccount(self, user, password):
         log.info("Adding account: %s", user)
         self.accounts.addUser(user, password)
         with open("creds.dat", "a+") as creds:
             creds.write("%s:%s\n"%(user, password))
 
-
+    @log_method
     def shutdown(self):
         log.info("Shutting down Server")
 
                       help="Interface to listen for new client connections")
     args.add_argument('-p', '--port', type=int,
                       help="Port to listen for new client connections")
-    args.add_argument('-v', '--verbose', action='count', default=0,
-                      help='Change the verbosity of the logging, each "-v" increases the ammount of logging '
-                           'by default this program will log at level INFO ("-vvv"). '
-                           '(levels: ERROR, WARN, INFO, DEBUG)')
     args.add_argument('-c', '--config', action='append', default=[],
-                      help="The config file used to set up the server")    
+                      help="The config file used to set up the server")
+
+    # Add reusable logging options
+    add_log_args(args)
+
     options = args.parse_args(argv)
 
-    # Logging
-    import logging
-    root_log = getLogger('pyshipcommand')
-    root_log.setLevel({0: logging.INFO, #default
-                       1: logging.ERROR,
-                       2: logging.WARN,
-                       3: logging.INFO,
-                       4: logging.DEBUG,
-                      }.get(min(4, options.verbose), 0))
-    handler = logging.StreamHandler()
-    handler.setFormatter(logging.Formatter('%(asctime)10s.%(msecs)3d %(levelname)-6s %(name)-24s %(message)s', '%c'))
-    root_log.addHandler(handler)
+    # Apply log options
+    set_log_options(options, 'pyshipcommand')
 
     # Config file
     from ConfigParser import SafeConfigParser as ConfigParser, NoSectionError
 if __name__ == "__main__":
     sys.exit(main())
 
-# end __main__
-
+# end __main__

File pyshipcommand/ship.py

View file
             return self.aux_equip.get(type_, None)
         return e
         
-    def setActiveScript(self, script_name):
+    def setActiveScript(self, script_name, reboot = False):
         scm = getUtility(IManager, "Script")
         script = scm.getScript(self.owner, script_name)
 
             self.namespace["ship"] = SafeShip(self)
             self.data.initialized = False
 
+            if reboot:
+                # Wipe ships data. This will also remove the last_script
+                # reference set above, but that is only used for upgrades anyway.
+                self.data = Ship.Data()
+
             return True
         return False
         

File pyshipcommand/universe.py

View file
 from djlib.spatial import ExpandingRectTree
 import random
 
-from logging import getLogger
+from djlib.logger import getLogger, log_method
 log = getLogger('pyshipcommand.universe')
 
 import celestial
         self.static_tree = ExpandingRectTree(Rect.fromSides(-halfSize, -halfSize, halfSize, halfSize))
         self.entity_by_id = {}
 
+    @log_method(log)
     def load(self, filename):
     	log.info("Loading Universe from %s", filename)
 
 
+    @log_method(log)
     def addDynamic(self, entity):
     	if not isinstance(entity, Entity):
     		raise TypeError()
         self.entity_by_id[entity.id] = entity
     	return self.dyn_tree.insert(entity)
 
+
+    @log_method(log)
     def addStatic(self, entity):
         if not isinstance(entity, Entity):
             return TypeError()
         self.entity_by_id[entity.id] = entity
         return self.static_tree.insert(entity)
 
+
+    @log_method(log)
     def getDynamicInRange(self, pos, dist):
         # rough entity lookup through the RectTree
         bounds = Rect.fromSides(pos.x-dist, pos.y-dist, pos.x+dist, pos.y+dist)
 
         return [entity for entity in rough if pos.distanceApart(entity.getPosition()) <= dist]
 
+
+    @log_method(log)
     def getStaticInRange(self, pos, dist):
         # rough entity lookup through the RectTree
         bounds = Rect.fromSides(pos.x-dist, pos.y-dist, pos.x+dist, pos.y+dist)
 
         return [entity for entity in rough if pos.distanceApart(entity.getPosition()) <= dist]
 
+
+    @log_method(log)
     def getAllInRange(self, pos, dist, type_ = None):
 
         entities = []
 
         return entities
 
+
+    @log_method(log)
     def getAllStatic(self):
         return self.static_tree.getData()
 
+
+    @log_method(log)
     def getById(self, id):
         return self.entity_by_id.get(id)
 
+
+    @log_method(log)
     def findNewEmpire(self):
 
         pos = None
 
         return pos
 
+
+    @log_method(log)
     def theBigBang(self):
     	
         # Only called during initialization.

File pyshipcommand_gui/__init__.py

View file
 from twisted.internet._threadedselect import install as reactor_install
 reactor_install()
 
+# SYSTEM
+from os import path
+from glob import glob
+import sys
+
+# ADDITIONAL LIBRARIES
 from twisted.internet import reactor, task
-from twisted.spread import pb
+import pygame
+from ocempgui.widgets import TwistedRenderer
+
+# PYSHIPCOMMAND
+from djlib.game import GameClass
+import djlib.logger as logger
+log = logger.getLogger('pyshipcommand.gui')
 
-# pyshipcommand logic
-from pyshipcommand.network import PBClient, NetVector, NetShip, NetInit, NetScript, NetCelestialBody
 from cscripts import ClientScriptMgr
+from client import GUIClient
+from viewer import StarGenerator
+from states.login import LoginState
 
-# GUI libs
-import pygame
-from pygame import K_UP, K_DOWN, K_LEFT, K_RIGHT, K_RETURN, K_SLASH
+# CONSTANTS
+from pyshipcommand.server import DEFAULT_PORT
+DESIRED_FPS = 30.0
+SCREEN_SIZE = (800, 600)
+DEFAULT_SERVER_STEP = 1.0
 
-from ocempgui import widgets
-from ocempgui.widgets.Constants import SIG_INPUT, SIG_KEYDOWN, SIG_MOUSEDOWN, SIG_MOUSEMOVE
-from ocempgui.events import INotifyable
+class GUIGameClass(GameClass):
 
-from pkg_resources import resource_stream
-from os import path
-from glob import glob
+    def __init__(self):
+        GameClass.__init__(self)
+
+        # Global constants
+        self.DEFAULT_SERVER = "localhost"
+        self.DEFAULT_PORT = DEFAULT_PORT
+        self.DEFAULT_USER = "admin"
+        self.COLOR_BLACK = pygame.Color(0,0,0)
+        self.STAR_COLOR = pygame.Color(255, 255, 255)
+        self.SERVER_STEP = DEFAULT_SERVER_STEP
+
+        # Itemize GameClass variables for reference
+        self.rsurf = None
+        self.uisurf = None
+        self.uirender = None
+        self.fpsClock = None
+        self.client = None
+        self.stars = None
+        self.time = 0
+        self.time_step = 0
+        self.dfont = None
 
-# pyshipcommand_gui
-import dialogs
-from viewer import ShipViewer
+    def initialize(self):
 
+        # Init pygame and create main rendering surface
+        pygame.init()
+        self.rsurf = pygame.display.set_mode(SCREEN_SIZE)
+        pygame.display.set_caption("pyShipCommand")
 
-# CONSTANTS
-SERVER_ADDR = "localhost"
-from pyshipcommand.server import DEFAULT_PORT as SERVER_PORT
+        # Create UI Renderer that renders to a clone of the main surface
+        self.uirender = TwistedRenderer()
+        self.uisurf = self.rsurf.copy()
+        self.uirender.set_screen(self.uisurf)
+        self.uirender.set_color((0, 0, 0))
+        self.uirender.reactor = reactor
 
-DEFAULT_USER = "admin"
-DEFAULT_PASS = "password"
+        # Main Game Clock
+        self.fpsClock = pygame.time.Clock()
 
-DESIRED_FPS = 30.0
+        # Network client
+        self.client = GUIClient(self)
 
-# Init pygame and create main rendering surface
-pygame.init()
-g_rsurf = pygame.display.set_mode((800, 600))
-pygame.display.set_caption("pyShipCommand")
-
-# Create UI Renderer that renders to a clone of the main surface
-g_uirender = widgets.TwistedRenderer()
-g_uisurf = g_rsurf.copy()
-g_uirender.set_screen(g_uisurf)
-g_uirender.set_color((0, 0, 0))
-g_uirender.reactor = reactor
-
-fpsClock = pygame.time.Clock()
-
-ship_view = ShipViewer(g_rsurf)
-  
-# Resources
-blackColor = pygame.Color(0,0,0)              
-shipImg = resource_stream('pyshipcommand_gui', 'assets/raptor.png')
-shipImg = pygame.image.load(shipImg)
-shipImg = shipImg.convert_alpha()
-ship_view.SHIP_IMG = pygame.transform.rotozoom(shipImg, -90, 0.25)
-
-cmdImg = resource_stream('pyshipcommand_gui', 'assets/command.png')
-cmdImg = pygame.image.load(cmdImg)
-ship_view.MSHIP_IMG = cmdImg.convert_alpha()
-
-class BaseClient(PBClient):
-    def __init__(self):
-        PBClient.__init__(self)
+        # The Starfield is used by multiple states and should be created now
+        self.stars = StarGenerator((SCREEN_SIZE[0]/2, SCREEN_SIZE[1]/2),
+                                        SCREEN_SIZE, 25)
 
-    def remote_print(self, msg):
-        print msg
-#end BaseClient
+        # Load the default font
+        self.dfont = pygame.font.Font(pygame.font.get_default_font(), 12)
+        if not self.dfont:
+            log.error("Default Font not loaded!")
 
-class RegisterClient(BaseClient):
+        # Be sure to initialize base GameClass
+        GameClass.initialize(self)
 
-    def login(self, username, password):
-        self.username = username
-        self.password = password
-        BaseClient.login(self, "register", "account")
+    def update(self):
+        # Add any global/game specific updates here
+        self.fpsClock.tick()
+        self.time = pygame.time.get_ticks()
+        self.time_step = self.fpsClock.get_rawtime()/1000.0
 
-    def connected(self):
-        dfr = self.server.callRemote("registerAccount", self.username, self.password)
-        dfr.addCallback(self.disconnect)
+        GameClass.update(self)
 
-    def disconnect(self, n):
-        BaseClient.disconnect(self)
-#end RegisterClient
+    def shutdown(self):
+        GameClass.shutdown(self)
 
-pb.setUnjellyableForClass(NetShip, NetShip)
-pb.setUnjellyableForClass(NetInit, NetInit)
-pb.setUnjellyableForClass(NetScript, NetScript)
-pb.setUnjellyableForClass(NetCelestialBody, NetCelestialBody)
-class GUIClient(BaseClient, pb.Referenceable, INotifyable):
-    
-    def __init__(self):
-        BaseClient.__init__(self)
-        self.time = 0
-        self.cscript = ClientScriptMgr()
-
-        self.scrollRect = pygame.Rect(50, 50, 700, 500)
-        self.scrollDir = None
-    
-    def update(self):
-        fpsClock.tick()
-        dt = fpsClock.get_rawtime()/1000.0
-
-        # Handle map scrolling
-        if self.scrollDir:
-            ship_view.move(self.scrollDir)
-        
-        # Tick pygame event loop
-        #print dt
-        # Update the Ship Viewer
-        ship_view.update(dt)
-        
-        self.draw()
-        pygame.display.update()
-
-    def notify(self, event):
-        # Receive event notifications from ocempgui
-
-        if event.signal == SIG_KEYDOWN:
-            # Handle arrow keys
-            if event.data.key in (K_UP, K_DOWN, K_RIGHT, K_LEFT):
-                ship_view.move(event.data.key)
-            elif event.data.key in (K_RETURN, K_SLASH):
-                g_uirender.set_active_layer(self.editWnd.depth)
-                self.editWnd.set_focus()
-                if event.data.key is K_SLASH:
-                    self.editWnd.set_text('/')
-                    self.editWnd.set_caret(1)
-
-        elif event.signal == SIG_MOUSEDOWN:
-            # set keyboard focus
-            g_uirender.set_active_layer(0)
-
-            if event.data.button == 1: #Left Click
-                ship = ship_view.pickShip(event.data.pos)
-                self.shipWnd.setShip(ship)
-                self.scriptWnd.setShip(ship)
-                print str(ship)
-
-        elif event.signal == SIG_MOUSEMOVE:
-            # Set scroll direction
-            mp = event.data.pos
-            self.scrollDir = None
-            '''
-            if not self.scrollRect.collidepoint(mp):
-                if mp[0] < self.scrollRect.left:
-                    self.scrollDir = K_LEFT
-                elif mp[0] > self.scrollRect.right:
-                    self.scrollDir = K_RIGHT
-                elif mp[1] < self.scrollRect.top:
-                    self.scrollDir = K_UP
-                elif mp[1] > self.scrollRect.bottom:
-                    self.scrollDir = K_DOWN
-            '''
-
-
-    # Registered with the pyui Desktop and called during the pyui draw() processing
-    # before any windows are rendered
-    def draw(self):
-        # get render surface
-        rs = g_rsurf
-        rs.fill(blackColor)
-        
-        ship_view.draw(rs)
-        
-        # Now apply UI
-        #g_uirender.update()
-        rs.blit(g_uirender.screen, (0,0), None, pygame.BLEND_ADD)
-        
-        # Draw FPS over everything
-        ship_view.renderText(rs, (0,0), str(int(fpsClock.get_fps())))
-
-        
-    def connected(self):
-
-        # Set the local active player
-        if self.loginWnd:
-            # Destroy the login windows on successful connection
-            self.loginWnd.destroy()
-            self.loginWnd = None
-
-            # Show the rest of the UI now that we are logged in
-            g_uirender.add_widget(self.editWnd)
-            g_uirender.add_widget(self.scriptWnd)
-            g_uirender.add_widget(self.shipWnd)
-
-        dfr = self.server.callRemote("getClientInit")
-        dfr.addCallback(self.setClientInit)
-        
-    def processCmd(self, edit):
-
-        # Handle edit box
-        line = edit.text
-        edit.set_text("")
-        edit.set_dirty(1)
-        
-        # Parse the command
-        if not line or not len(line) > 0:
-            return
-
-        # Raw text should be treated as a chat message.
-        if line[0] != '/':
-            self.do_chat(line)
-            return
-
-        # Split line and remove leading '/'
-        commandParts = line[1:].split()
-        command = commandParts[0].lower()
-        args = commandParts[1:]
-
-        # Dispatch the command to the appropriate method.
-        try:
-            method = getattr(self, 'do_' + command)
-        except AttributeError, e:
-            print 'Error: no such command.'
-        else:
-            try:
-                method(*args)
-            except Exception, e:
-                    print 'Error', str(e)
-                    
-    def createDialogs(self):
-        (w,h) = g_rsurf.get_size()
-        
-        # CLI Entry
-        edit = widgets.Entry()
-        edit.topleft = (0, h-20)
-        edit.minsize = w, 20
-        edit.depth = 1
-        edit.connect_signal(SIG_INPUT, self.processCmd, edit)
-        #g_uirender.add_widget(edit)
-        self.editWnd = edit
-        
-        # Movement Widget
-        #g_uirender.add_widget(dialogs.MovementWidget(400, 0, ship_view.move))
-
-        # Script widget
-        self.scriptWnd = dialogs.ScriptWidget(w-205, 0, self.cscript, self.do_import,
-                                              self.do_upload, self.do_ship)
-        #g_uirender.add_widget(self.scriptWnd)
-        
-        # Create login dialog
-        self.loginWnd = dialogs.LoginDialog(self, RegisterClient(), w, h)
-        self.loginWnd.populateUser(DEFAULT_USER)
-        self.loginWnd.populateServer(SERVER_ADDR, SERVER_PORT)
-        g_uirender.add_widget(self.loginWnd)
-        self.loginWnd.set_focus()
-
-        # Ship Info
-        self.shipWnd = dialogs.ShipInfo(w-205, self.scriptWnd.height)
-        #g_uirender.add_widget(self.shipWnd)
-
-        # register this client to receive input notifications
-        event_mgr = g_uirender.get_managers()[0]
-        event_mgr.add_object(self, SIG_KEYDOWN, SIG_MOUSEDOWN, SIG_MOUSEMOVE)
-                    
-    def do_cl(self):
-        self.connect(SERVER_ADDR, SERVER_PORT)
-        self.login(DEFAULT_USER, DEFAULT_PASS)
-                    
-    def do_connect(self, addr = SERVER_ADDR, port = SERVER_PORT):
-        self.connect(addr, port)
-        
-    def do_login(self, user = DEFAULT_USER, password = DEFAULT_PASS):
-        self.login(user, password)
-
-    def do_help(self, command=None):
-        """help [command]: List commands, or show help on the given command"""
-        if command:
-            print (getattr(self, 'do_' + command).__doc__)
-        else:
-            commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')]
-            print ("Valid commands: " +" ".join(commands))
-
-    def do_exit(self):
-        self.disconnect()
+        self.client.disconnect()
         pygame.quit()
 
-    def do_info(self):
-        self.server.callRemote("info")
-
-    def do_upload(self, scriptfile):
-
-        # Sanity checks
-        if not scriptfile:
-            raise Exception("script <script_data>")
-
-        s = self.cscript.getScript(scriptfile)
-        if s:
-            print "sending script:", s.name
-            self.server.callRemote("giveScript", s.name, s.source)
-            return
-
-        print scriptfile + " not found!"
-
-
-    def do_import(self, scriptPath = None):
-
-        if not scriptPath:
-            script_dlg = dialogs.ImportScriptDialog(self.do_import, *g_rsurf.get_size())
-            g_uirender.add_widget(script_dlg)
-            script_dlg.set_focus()
-            return
-
-        # Always treat scripts as a list, even if they only specified a file.
-        scriptFiles = [scriptPath]
-        if path.isdir(scriptPath):
-            scriptFiles = glob(path.join(scriptPath,"*.py"))
-
-        for s in scriptFiles:
-            self.cscript.importScript(s)
-
-        # Update list contents, even if we could have failed
-        if len(scriptFiles) > 0:
-            self.scriptWnd.update_list()
-            #self.scriptWnd.select_script(s.name)
-            return
-
-        print "Could not import:" + scriptFiles
-
-    def do_reload(self):
-
-        self.cscript.reloadScripts()
-        self.scriptWnd.update_list()
-
-
-    def do_ship(self, cmd, *args):
-        print "do_ship", cmd, ":", args
-        if cmd == "create":
-            pos = NetVector(0.0, 0.0)
-            if len(args) == 2:
-                pos = NetVector(float(args[0]), float(args[1]))
-            print pos
-            self.server.callRemote("addShip", pos)
-        elif cmd == "move":
-            ship_id = int(args[0])
-            target_pos = NetVector(float(args[1]), float(args[2]))
-            print target_pos
-            self.server.callRemote("moveShip", ship_id, target_pos)
-        elif cmd == "orbit":
-            ship_id = int(args[0])
-            target_pos = NetVector(float(args[1]), float(args[2]))
-            radius = float(args[3])
-            self.server.callRemote("orbitShip", ship_id, target_pos, radius)
-        elif cmd == "follow":
-            ship_id = int(args[0])
-            target_id = int(args[1])
-            dist = float(args[2])
-            self.server.callRemote("followShip", ship_id, target_id, dist)
-        elif cmd == "script":
-            ship_id = int(args[0])
-            script_name = args[1]
-            self.server.callRemote("scriptShip", ship_id, script_name)
-
-    def do_scroll(self, *args):
-        scroll_speed = ShipViewer.SCROLL_SPEED
-        if len(args) == 1:
-            scroll_speed = int(args[0])
-        ship_view.SCROLL_SPEED = scroll_speed
-
-    def do_msg(self, *args):
-        print "do_msg, ", args
-        self.server.callRemote("giveMessage", args)
-
-    def do_home(self):
-        ship_view.centerView()
-
-    def do_chat(self, msg):
-        self.server.callRemote("giveChat", msg)
-
-    def do_players(self):
-        self.server.callRemote("getPlayers")
-        
-    def remote_updateShips(self, net_ships):
-        # Sanity check
-        if not net_ships:
-            return
-        print "Received %d ship updates!" % (len(net_ships))
-        ship_view.updateShips(net_ships)
-
-    def remote_log(self, log):
-        for l in log:
-            print l
-
-    def remote_showText(self, text):
-        ship_view.delayedText(text, 5.0)
-
-    def setClientInit(self, init_data):
-
-        # Share init data with ship view
-        ship_view.setClientInit(init_data)
-
-        # Load player scripts
-        self.cscript.setActivePlayer(init_data.player)
-        print "Scripts:", self.cscript.getScriptList()
-        self.scriptWnd.update_list()
-
-        # Cache current server scripts for player
-        self.cscript.setServerScripts(init_data.scripts)
-
-        
-#end GUIClient
-
-def main():
-
-    client = GUIClient()
-    client.REF = client
-    
-    client.createDialogs()
+    def quit(self):
+        # tell systems to shut down
+        # normally we would just set self.running to False
+        # but our update loop is based on the Twisted Reactor
+        # Use pygame to give it the QUIT signal
+        e = pygame.event.Event(pygame.QUIT)
+        pygame.event.post(e)
+
+#end GUIGameClass
+
+def main(argv=sys.argv[1:]):
+    from argparse import ArgumentParser, FileType
+
+    # Argument parsing
+    args = ArgumentParser()
+    logger.add_log_args(args)
+
+    options = args.parse_args(argv)
+    logger.set_log_options(options, 'pyshipcommand')
+
+    # MAIN GAMECLASS
+    game = GUIGameClass()
     
+    #shipImg = resource_stream('pyshipcommand_gui', 'assets/raptor.png')
+    #shipImg = pygame.image.load(shipImg)
+    #shipImg = shipImg.convert_alpha()
+    #ship_view.SHIP_IMG = pygame.transform.rotozoom(shipImg, -90, 0.25)
+
+    #cmdImg = resource_stream('pyshipcommand_gui', 'assets/command.png')
+    #cmdImg = pygame.image.load(cmdImg)
+    #ship_view.MSHIP_IMG = cmdImg.convert_alpha()
+
     # Manually connect
     #client.connect(SERVER_ADDR, SERVER_PORT)
     #client.login(DEFAULT_USER, DEFAULT_PASS)
+
+    # Initialize
+    game.initialize()
+
+    # Create and switch to starting state
+    game.changeState(LoginState)
     
-    # trigger update loop
-    client.update_task = task.LoopingCall(client.update)
-    client.update_task.start(1.0/DESIRED_FPS)
-    g_uirender.start()
+    # Update loop is slightly different than normal.
+    # Instead of a running while True loop, we register
+    # an asynchronous task that runs at our desired frame rate
+    update_task = task.LoopingCall(game.update)
+    update_task.start(1.0/DESIRED_FPS)
+
+    # blocks until reactor is shutdown.
+    game.uirender.start()
 
-    client.disconnect()
+    update_task.stop()
+    game.shutdown()
 #end main
 
 if __name__ == "__main__":

File pyshipcommand_gui/assets/pyship_logo.png

Added
New image

File pyshipcommand_gui/client.py

View file
+
+# SYSTEM
+
+# ADDITIONAL LIBRARIES
+from twisted.spread import pb
+
+# LOCAL
+from states.loading import LoadingState
+from states.viewer import ViewerState
+from cscripts import ClientScriptMgr
+from pyshipcommand.network import PBClient, NetVector, NetShip, NetInit, NetScript, NetCelestialBody
+
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.client')
+
+class BaseClient(PBClient):
+    def __init__(self):
+        PBClient.__init__(self)
+
+    def remote_print(self, msg):
+        print msg
+#end BaseClient
+
+class RegisterClient(BaseClient):
+
+    def login(self, username, password):
+        self.username = username
+        self.password = password
+        BaseClient.login(self, "register", "account")
+
+    def connected(self):
+        dfr = self.server.callRemote("registerAccount", self.username, self.password)
+        dfr.addCallback(self.disconnect)
+
+    def disconnect(self, n):
+        BaseClient.disconnect(self)
+#end RegisterClient
+
+pb.setUnjellyableForClass(NetShip, NetShip)
+pb.setUnjellyableForClass(NetInit, NetInit)
+pb.setUnjellyableForClass(NetScript, NetScript)
+pb.setUnjellyableForClass(NetCelestialBody, NetCelestialBody)
+class GUIClient(BaseClient, pb.Referenceable):
+    
+    def __init__(self, gameclass):
+        BaseClient.__init__(self)
+        self.REF = self
+        self.gc = gameclass
+
+        self.scripts = ClientScriptMgr()
+
+    def connected(self):
+        self.gc.changeState(LoadingState)
+
+    def exit(self):
+        self.gc.quit()
+
+    # SERVER COMMUNICATION
+    def info(self):
+        self.server.callRemote("info")
+
+    def giveScript(self, name, source):
+        self.server.callRemote("giveScript", name, source)
+
+    def addShip(self, pos):
+        self.server.callRemote("addShip", NetVector.fromVector(pos))
+
+    def moveShip(self, ship_id, target_pos):
+        self.server.callRemote("moveShip", ship_id, NetVector.fromVector(target_pos))
+
+    def orbitShip(self, ship_id, target_pos, radius):
+        self.server.callRemote("orbitShip", ship_id, NetVector.fromVector(target_pos), radius)
+
+    def followShip(self, ship_id, target_id, dist):
+        self.server.callRemote("followShip", ship_id, target_id, dist)
+
+    def scriptShip(self, ship_id, script_name, action):
+        self.server.callRemote("scriptShip", ship_id, script_name, action)
+
+    def giveMessage(self, msg):
+        self.server.callRemote("giveMessage", msg)
+
+    def giveChat(self, msg):
+        self.server.callRemote("giveChat", msg)
+
+    def getPlayers(self):
+        self.server.callRemote("getPlayers")
+
+    def getClientInit(self, cb):
+        dfr = self.server.callRemote("getClientInit")
+        dfr.addCallback(self.setClientInit)
+        dfr.addCallback(cb)
+
+    # CLIENT CALLBACKS
+
+    def remote_updateShips(self, net_ships):
+        # Sanity check
+        if not net_ships:
+            return
+        log.debug("Received %d ship updates!", len(net_ships))
+
+        # We only use ship updates when we are in the ViewerState
+        state = self.gc.getActiveState()
+        if isinstance(state, ViewerState):
+            state.updateShips(net_ships)
+
+    def remote_log(self, log):
+        for l in log:
+            print l
+
+    def remote_showText(self, text):
+        # We only send text when in ViewerState
+        state = self.gc.getActiveState()
+        if isinstance(state, ViewerState):
+            state.delayedText(text, 5000)
+
+    def setClientInit(self, init_data):
+        # Load player scripts
+        self.scripts.setActivePlayer(init_data.player)
+        log.info("Scripts: %s", self.scripts.getScriptList())
+
+        # Cache current server scripts for player
+        self.scripts.setServerScripts(init_data.scripts)
+        return init_data
+
+#end GUIClient

File pyshipcommand_gui/cscripts.py

View file
 
-from pyshipcommand.scripts import Script, ScriptMgr
+# SYSTEM
 import os.path
 
+# ADDITIONAL LIBRARIES
+
+# PYSHIPCOMMAND
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.cscripts')
+
+from pyshipcommand.scripts import Script, ScriptMgr
+
 # CONSTANTS
 SCRIPT_DIR = "cscripts"
 
 				if ls.lastHash == s.last_hash:
 					ls.version = s.version
 				else:
-					print "Script %s[%s] not in sync with server [%s]!" % (ls.name, ls.lastHash, s.last_hash)
+					log.error("Script %s[%s] not in sync with server [%s]!", ls.name, ls.lastHash, s.last_hash)
 
 	def setActivePlayer(self, player):
 
 			name = os.path.splitext(os.path.basename(scriptfile))[0]
 			data = f.read()
 
-			print "importing script:" + name
+			log.debug("importing script: %s", name)
 			self.giveScript(name, data)
 			return self.getScript(name)
 		return None
 
 		player_dir = os.path.join(self.smgr.script_dir, self.curr_player)
 		return self.smgr._loadPlayerScripts(self.curr_player, player_dir)
-
-
 #end ClientScriptMgr
 
 

File pyshipcommand_gui/dialogs.py

View file
 
+# ADDITIONAL LIBRARIES
 from ocempgui import widgets
 from ocempgui.widgets.Constants import *
 
+# LOCAL
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.dialogs')
+
 class ShipInfoWidget(widgets.Table):
     
     FRAME_WIDTH = 200
         exitBtn.connect_signal(SIG_CLICKED, self._quit)
         self.child.add_child(6, 0, exitBtn)
         
-        # Center dialog on screen
-        self.topleft = ((screen_width-self.width)/2, (screen_height-self.height)/2)
+        # Position dialog in middle/lower half of screen
+        self.topleft = ((screen_width-self.width)/2, screen_height/2)
         
     def populateUser(self, username):
         self.userEntry.set_text(username)
 
         
     def _quit(self):
-        self.client.do_exit()
+        self.client.exit()
         
 #end LoginDialog
 
         widgets.Table.notify(self, event)
         
     def _buttonCB(self, button):
-        print button.text
+        log.debug(button.text)
 #end MovementWidget
 
 
     def select_script(self, script):
         for i in self.scrollWnd.items:
             if i.text == script:
-                print "Selecting " + script
+                log.debug("Selecting %s", script)
                 self.scrollWnd.select(i)
                 return
 
     def _upload_item(self, slist, button):
         for s in slist.get_selected():
             self.upload_cb(s.text.split()[0])
-            print s.text
         button.sensitive = False
 
     def _update_button(self, slist, button):

File pyshipcommand_gui/states/__init__.py

Empty file added.

File pyshipcommand_gui/states/loading.py

View file
+
+# SYSTEM
+from random import random
+
+# ADDITIONAL LIBRARIES
+import pygame
+
+# LOCAL
+
+from djlib.game import GameState
+from djlib.primitives import Vector
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.state.loading')
+
+from viewer import ViewerState
+
+# CONSTANTS
+MIN_LOADING = 1.0
+STAR_SPEED = 1.0
+STAR_BLUR = 5
+
+# TestState
+class LoadingState(GameState):
+
+    def __init__(self):
+        GameState.__init__(self)
+
+        # Itemize LoginState variables for reference
+
+    def initialize(self):
+        """Called the first time the game is changed to this state
+           during the applications lifecycle."""
+
+    def enter(self):
+        """Called every time the game is switched to this state."""
+        self.gc.client.getClientInit(self.setClientInit)
+
+    def processInput(self):
+        """Called during normal update/render period for this state
+           to process it's input."""
+
+    def update(self):
+        """Called during normal update/render period for this state
+           to update it's local or game data."""
+
+    def render(self):
+        """Called during normal update/render period for this state
+           to render it's data in a specific way."""
+        # get render surface
+        rs = self.gc.rsurf
+        rs.fill(self.gc.STAR_COLOR)
+
+        # Now apply UI
+        #rs.blit(self.gc.uirender.screen, (0,0), None, pygame.BLEND_ADD)
+
+        pygame.display.update()
+
+    def leave(self):
+        """Called whenever we switch from this state to another."""
+
+    def shutdown(self):
+        """Called during application shutdown."""
+
+    def setClientInit(self, client_init):
+        self.changeState(ViewerState, client_init)
+
+
+#end TestState
+

File pyshipcommand_gui/states/login.py

View file
+
+# SYSTEM
+from math import pi
+from pkg_resources import resource_stream
+
+# ADDITIONAL LIBRARIES
+import pygame
+
+# LOCAL
+from pyshipcommand_gui.dialogs import LoginDialog
+from pyshipcommand_gui.client import RegisterClient
+
+from djlib.game import GameState
+from djlib.primitives import Circle, Point, Rect
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.state.login')
+
+# CONSTANTS
+TITLE_POS = (100, 50)
+ORBIT_RADIUS = 500
+ORBIT_SPEED = 0.04
+
+class LoginState(GameState):
+
+    def __init__(self):
+        GameState.__init__(self)
+
+        # Itemize LoginState variables for reference
+        self.titleImg = None
+        self.loginWnd = None
+        self.star_period = 0.0
+
+    def initialize(self):
+        """Called the first time the game is changed to this state
+           during the applications lifecycle."""
+        rs = resource_stream('pyshipcommand_gui', 'assets/pyship_logo.png')
+        self.titleImg = pygame.image.load(rs)
+
+        # Create Login Dialog
+        (w,h) = self.gc.rsurf.get_size()
+        self.loginWnd = LoginDialog(self.gc.client, RegisterClient(), w, h)
+        self.loginWnd.populateUser(self.gc.DEFAULT_USER)
+        self.loginWnd.populateServer(self.gc.DEFAULT_SERVER, self.gc.DEFAULT_PORT)
+
+    def enter(self):
+        """Called every time the game is switched to this state."""
+        self.gc.uirender.add_widget(self.loginWnd)
+        #self.loginWnd.resetPass()
+        self.loginWnd.set_focus()
+
+    def processInput(self):
+        """Called during normal update/render period for this state
+           to process it's input."""
+
+    def update(self):
+        """Called during normal update/render period for this state
+           to update it's local or game data."""
+        self.star_period += ORBIT_SPEED * self.gc.time_step
+        if self.star_period > (2.0*pi):
+            self.star_period = self.star_period - (2.0*pi)
+
+    def render(self):
+        """Called during normal update/render period for this state
+           to render it's data in a specific way."""
+        # get render surface
+        rs = self.gc.rsurf
+        rs.fill(self.gc.COLOR_BLACK)
+
+        # update stars
+        star_orbit = Circle(Point(0,0), ORBIT_RADIUS)
+        star_pos = star_orbit.pointOnCircle(self.star_period)
+        star_pos = Point(int(star_pos[0]), int(star_pos[1]))
+        star_view = Rect.fromPointSize(star_pos,
+                                       *self.gc.rsurf.get_size())
+        #print str(star_view)
+
+        self.gc.stars.setView(star_view)
+        self.gc.stars.draw(rs, star_view)
+
+        # Draw title image if present
+        if self.loginWnd:
+            rs.blit(self.titleImg, TITLE_POS)
+
+        # Now apply UI
+        #g_uirender.update()
+        rs.blit(self.gc.uirender.screen, (0,0), None, pygame.BLEND_ADD)
+
+        pygame.display.update()
+
+    def leave(self):
+        """Called whenever we switch from this state to another."""
+        self.gc.uirender.remove_widget(self.loginWnd)
+
+    def shutdown(self):
+        """Called during application shutdown."""
+        self.loginWnd.destroy()
+
+#end LoginState
+

File pyshipcommand_gui/states/viewer.py

View file
+
+# SYSTEM
+from pkg_resources import resource_stream
+from os import path
+from glob import glob
+
+# ADDITIONAL LIBRARIES
+import pygame
+from pygame import K_UP, K_DOWN, K_LEFT, K_RIGHT, K_RETURN, K_SLASH
+
+from ocempgui import widgets
+from ocempgui.widgets.Constants import SIG_INPUT, SIG_KEYDOWN, SIG_MOUSEDOWN, SIG_MOUSEMOVE
+from ocempgui.events import INotifyable
+
+# LOCAL
+from djlib.game import GameState
+from djlib.primitives import Vector, Rect
+from djlib.spatial import ExpandingRectTree
+from pyshipcommand_gui.viewer import ShipViewer
+import pyshipcommand_gui.dialogs as dialogs
+
+from djlib.logger import getLogger
+log = getLogger('pyshipcommand.gui.state.viewer')
+
+# TestState
+class ViewerState(GameState, INotifyable):
+
+    # CONSTANTS
+    SCROLL_RECT_OFFSET = 50
+    SCROLL_SPEED = 5
+    SHIP_RADIUS = 30
+
+    # ASSETS
+    SHIP_IMG = None
+    MSHIP_IMG = None
+
+    SUN_COLOR = pygame.Color(255, 255, 0)
+    PLANET_COLOR = pygame.Color(0, 128, 255)
+    ASTEROID_COLOR = pygame.Color(128, 128, 128)
+
+    SEL_COLOR = pygame.Color(0, 255, 0)
+
+    def __init__(self):
+        GameState.__init__(self)
+
+        # Itemize ViewerState members for reference
+        self.scroll_rect = None
+        self.scroll_dir = None
+
+        self.ship_view = None
+        self.sel_ship = None
+
+        self.delay_text = None
+
+        self.universe = None
+        self.view_rect = None
+        self.tree = None
+        self.view_node = None
+
+        self.script_wnd = None
+        self.ship_wnd = None
+        self.cmd_wnd = None
+
+    def initialize(self):
+        """Called the first time the game is changed to this state
+           during the applications lifecycle."""
+        (w,h) = self.gc.rsurf.get_size()
+        self.view_rect = Rect.fromSides(0, 0, w, h)
+        self.tree = ExpandingRectTree(Rect.fromSides(0, 0, w, h))
+
+        self.delay_text = []
+
+        # Resources       
+        shipImg = resource_stream('pyshipcommand_gui', 'assets/raptor.png')
+        shipImg = pygame.image.load(shipImg)
+        shipImg = shipImg.convert_alpha()
+        self.SHIP_IMG = pygame.transform.rotozoom(shipImg, -90, 0.25)
+
+        cmdImg = resource_stream('pyshipcommand_gui', 'assets/command.png')
+        cmdImg = pygame.image.load(cmdImg)
+        self.MSHIP_IMG = cmdImg.convert_alpha()
+
+        # Create ship, script, and cmd dialogs
+        self._createDialogs()
+
+    def enter(self, client_init):
+        """Called every time the game is switched to this state."""
+        self.ship_view = ShipViewer(self.gc.rsurf)
+        self.ship_view.setClientInit(client_init)
+        self.ship_view.SHIP_IMG = self.SHIP_IMG
+        self.ship_view.MSHIP_IMG = self.MSHIP_IMG
+        self.ship_view.FONT = self.gc.dfont
+
+        self.updateUniverse(client_init.universe)
+        self.centerView(client_init.start_pos)
+
+        self.scroll_dir = None
+
+        # Show dialogs
+        ui = self.gc.uirender
+        ui.add_widget(self.cmd_wnd)
+        ui.add_widget(self.script_wnd)
+        ui.add_widget(self.ship_wnd)
+
+        # Update dialogs
+        self.script_wnd.update_list()
+
+        # Register for input messages
+        event_mgr = self.gc.uirender.get_managers()[0]
+        event_mgr.add_object(self, SIG_KEYDOWN, SIG_MOUSEDOWN, SIG_MOUSEMOVE)
+
+    def notify(self, event):
+        if event.signal == SIG_KEYDOWN:
+            # Handle arrow keys
+            if event.data.key in (K_UP, K_DOWN, K_RIGHT, K_LEFT):
+                self.move(event.data.key)
+            elif event.data.key in (K_RETURN, K_SLASH):
+                self.gc.uirender.set_active_layer(self.cmd_wnd.depth)
+                self.cmd_wnd.set_focus()
+                if event.data.key is K_SLASH:
+                    self.cmd_wnd.set_text('/')
+                    self.cmd_wnd.set_caret(1)
+            
+        elif event.signal == SIG_MOUSEDOWN:
+            # set keyboard focus
+            self.gc.uirender.set_active_layer(0)
+
+            if event.data.button == 1: #Left Click
+                real_pos = Vector(*event.data.pos) + self.view_rect.pos
+                self.sel_ship = self.ship_view.pickShip(real_pos)
+                self.ship_wnd.setShip(self.sel_ship)
+                self.script_wnd.setShip(self.sel_ship)
+                log.debug("Selected %s", str(self.sel_ship))
+
+        elif event.signal == SIG_MOUSEMOVE:
+            # Set scroll direction
+            mp = event.data.pos
+            self.scrollDir = None
+            '''
+            if not self.scrollRect.collidepoint(mp):
+                if mp[0] < self.scrollRect.left:
+                    self.scrollDir = K_LEFT
+                elif mp[0] > self.scrollRect.right:
+                    self.scrollDir = K_RIGHT
+                elif mp[1] < self.scrollRect.top:
+                    self.scrollDir = K_UP
+                elif mp[1] > self.scrollRect.bottom:
+                    self.scrollDir = K_DOWN
+            '''
+
+    def processInput(self):
+        """Called during normal update/render period for this state
+           to process it's input."""
+
+        # Handle map scrolling
+        if self.scroll_dir:
+            self.move(self.scroll_dir)
+
+    def update(self):
+        """Called during normal update/render period for this state
+           to update it's local or game data."""
+
+        # update the ship view
+        self.ship_view.update(self.gc.time_step)
+
+        # Remove any expired delay text
+        while len(self.delay_text) > 0 and self.gc.time > self.delay_text[0][0]:
+            del self.delay_text[0]
+
+    def render(self):
+        """Called during normal update/render period for this state
+           to render it's data in a specific way."""
+        # get render surface
+        rs = self.gc.rsurf
+        rs.fill(self.gc.COLOR_BLACK)
+
+        pos = -self.view_rect.pos
+
+        # Draw stars
+        self.gc.stars.draw(rs, self.view_rect)
+
+        # Draw universe
+        if self.universe:
+            uni_node = self.universe.minNode(self.view_rect)
+            if not uni_node:
+                uni_node = self.universe
+            self.ship_view.drawTree(rs, uni_node, pos)
+
+            colors = [self.SUN_COLOR, self.PLANET_COLOR, self.ASTEROID_COLOR]
+
+            for body in uni_node:
+                #ignore unknown types
+                if body.type < 0:
+                    continue
+
+                pygame.draw.circle(rs, colors[body.type], (pos+body.pos).intArgs(), body.radius)
+                #pygame.draw.circle(rs, colors[body.type], (100 * body.type,100), body.radius)
+
+        # Draw ships
+        self.ship_view.draw(rs, self.view_rect)
+
+        # draw selection
+        if self.sel_ship:
+            pygame.draw.circle(rs, self.SEL_COLOR, (pos+self.sel_ship.pos).intArgs(), self.SHIP_RADIUS, 2)
+
+        # draw delayed text
+        drawPos = Vector(20, self.view_rect.height() - 50)
+        for dText in self.delay_text[::-1]:
+            self.ship_view.renderText(rs, drawPos.intArgs(), dText[1])
+            drawPos -= Vector(0, 20)
+
+        # draw current FPS over everything
+        self.ship_view.renderText(rs, (0,0), str(int(self.gc.fpsClock.get_fps())))
+
+        # Now apply UI
+        rs.blit(self.gc.uirender.screen, (0,0), None, pygame.BLEND_ADD)
+
+        pygame.display.update()
+
+    def delayedText(self, text, delay):
+        if not isinstance(text, list):
+            text = [str(text)]
+            
+        for t in text:
+            self.delay_text.append( (self.gc.time+delay, t) )
+        log.debug(self.delay_text)
+
+    def leave(self):
+        """Called whenever we switch from this state to another."""
+        # Unregister for input messages
+        event_mgr = self.gc.uirender.get_managers()[0]
+        event_mgr.remove_object(self, SIG_KEYDOWN, SIG_MOUSEDOWN, SIG_MOUSEMOVE)
+
+        # Hide dialogs
+        ui = self.gc.uirender
+        ui.remove_widget(self.cmd_wnd)
+        ui.remove_widget(self.script_wnd)
+        ui.remove_widget(self.ship_wnd)
+
+        self.ship_view = None
+
+    def shutdown(self):
+        """Called during application shutdown."""
+        self.font = None
+        self.SHIP_IMG = None
+        self.MSHIP_IMG = None
+
+    def updateUniverse(self, net_bodies):
+
+        # for now, we only should get one universe update
+        # so overwrite any existing data
+        w, h = self.view_rect.width(), self.view_rect.height()
+        self.universe = ExpandingRectTree(Rect.fromSides(0, 0, w, h))
+        body_count = [0]*4
+        for body in net_bodies:
+            self.universe.insert(body)
+            body_count[body.type] += 1
+            #print "Body[%d] %s(%d)" % (body.type, str(body.pos), body.radius)
+
+        log.info("Universe contains %d Suns, %d Planets, %d Asteroids, %d Unknowns", *tuple(body_count))
+
+    def updateShips(self, net_ships):
+        self.ship_view.updateShips(net_ships)
+
+    def move(self, event):
+        vec = Vector(0, 0)
+        if event == pygame.K_UP:
+            vec = Vector(0, -self.SCROLL_SPEED)
+        elif event == pygame.K_DOWN:
+            vec = Vector(0, self.SCROLL_SPEED)
+            
+        if event == pygame.K_LEFT:
+            vec = Vector(-self.SCROLL_SPEED, 0)
+        elif event == pygame.K_RIGHT:
+            vec = Vector(self.SCROLL_SPEED, 0)
+            
+        self.view_rect.move(vec)
+        self.gc.stars.setView(self.view_rect)
+
+
+    def centerView(self, pos=None):
+
+        # Use mothership pos if no pos is specified
+        if not pos:
+            pos = self.ships[self.ship_view.mship].pos
+
+        d = pos - self.view_rect.center()
+        log.debug("%s -> %s", self.view_rect.center(), pos)
+        self.view_rect.move(d)
+        log.debug(self.view_rect)
+        self.gc.stars.setView(self.view_rect, True)
+
+    def _createDialogs(self):
+        (w,h) = self.gc.rsurf.get_size()
+        
+        # CLI Entry
+        edit = widgets.Entry()
+        edit.topleft = (0, h-20)
+        edit.minsize = w, 20
+        edit.depth = 1
+        edit.connect_signal(SIG_INPUT, self.processCmd, edit)
+        self.cmd_wnd = edit
+
+        # Script widget
+        self.script_wnd = dialogs.ScriptWidget(w-205, 0, self.gc.client.scripts,
+                                              self.cmd_import,
+                                              self.cmd_upload, self.cmd_ship)
+
+        # Ship Info
+        self.ship_wnd = dialogs.ShipInfo(w-205, self.script_wnd.height)
+
+    # CMD HANDLERS
+
+    def processCmd(self, edit):
+
+        # Handle edit box
+        line = edit.text
+        edit.set_text("")
+        edit.set_dirty(1)
+        
+        # Parse the command
+        if not line or not len(line) > 0:
+            return
+
+        # Raw text should be treated as a chat message.
+        if line[0] != '/':
+            self.cmd_chat(line)
+            return
+
+        # Split line and remove leading '/'
+        commandParts = line[1:].split()
+        command = commandParts[0].lower()
+        args = commandParts[1:]
+
+        # Dispatch the command to the appropriate method.
+        try:
+            method = getattr(self, 'cmd_' + command)
+        except AttributeError, e:
+            print 'Error: no such command.'
+        else:
+            try:
+                method(*args)
+            except Exception, e:
+                    log.error('Error %s', str(e))
+
+    def cmd_help(self, command=None):
+        """help [command]: List commands, or show help on the given command"""
+        if command:
+            print (getattr(self, 'cmd_' + command).__doc__)
+        else:
+            commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('cmd_')]
+            print ("Valid commands: " +" ".join(commands))
+
+    def cmd_exit(self):
+        self.gc.quit()
+
+    def cmd_info(self):
+        self.gc.client.info()
+
+    def cmd_upload(self, scriptfile):
+
+        # Sanity checks
+        if not scriptfile:
+            raise Exception("script <script_data>")
+
+        s = self.gc.client.scripts.getScript(scriptfile)
+        if s:
+            log.info("sending script: %s", s.name)
+            self.gc.client.giveScript(s.name, s.source)
+            return
+
+        log.error("%s not found!", scriptfile)
+
+
+    def cmd_import(self, scriptPath = None):
+
+        if not scriptPath:
+            script_dlg = dialogs.ImportScriptDialog(self.cmd_import, *self.gc.rsurf.get_size())
+            self.gc.uirender.add_widget(script_dlg)
+            script_dlg.set_focus()
+            return