def __init__(self, game, worldsize, objlistener=None): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not objlistener: initial listner callback function """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler( begin=self.__beginCollideObject, post_solve=self.__collideObject, separate=self.__endCollideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__influential = [] if objlistener == None: self.__objectListener = [] else: self.__objectListener = [objlistener] self.__toremove = [] self.__toadd = [] self.__active = False self.__started = False self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize))
def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False self.__created = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean( "Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean( "Game", "secondary_victory_highest") self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tmanager = eval(cfgobj.get("Tournament", "manager"))( cfgobj, self.game_get_current_leader_list) if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = GameWorld(self, (cfgobj.getint( "World", "width"), cfgobj.getint("World", "height")), self.world_add_remove_object) self.spawnmanager = SpawnManager(cfgobj, self.world) if self.__autostart: self.round_start()
def __init__(self, cfgobj): self._outposts = ThreadSafeDict() super(DiscoveryQuestGame, self).__init__(cfgobj) self._missions = cfgobj.get("DiscoveryQuest", "mission_objectives").split(",") self.scantime = cfgobj.getfloat("DiscoveryQuest", "scan_time") self.scanrange = cfgobj.getint("DiscoveryQuest", "scan_range") self.outpostdist = cfgobj.getint("DiscoveryQuest", "ship_spawn_dist") self.limitwarp = cfgobj.getboolean("DiscoveryQuest", "disable_warp_in_nebula")
def __init__(self, cfgobj): self._respawn = cfgobj.getboolean("BaubleHunt", "respawn_bauble_on_collect") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self._mv = BaseBaubleGame.VALUE_TABLE[-1][1] self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size")
def __init__(self, cfgobj): bb = cfgobj.getfloat("BaubleHunt", "bauble_percent_blue") yb = cfgobj.getfloat("BaubleHunt", "bauble_percent_yellow") rb = cfgobj.getfloat("BaubleHunt", "bauble_percent_red") self._mv = cfgobj.getint("BaubleHunt", "bauble_points_red") self.__valuetable = [(bb, cfgobj.getint("BaubleHunt", "bauble_points_blue")), (bb+yb, cfgobj.getint("BaubleHunt", "bauble_points_yellow")), (bb+yb+rb, self._mv)] self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size")
def __init__(self, cfgobj): bb = cfgobj.getfloat("BaubleHunt", "bauble_percent_blue") yb = cfgobj.getfloat("BaubleHunt", "bauble_percent_yellow") rb = cfgobj.getfloat("BaubleHunt", "bauble_percent_red") self._mv = cfgobj.getint("BaubleHunt", "bauble_points_red") BaubleHuntGame.VALUE_TABLE = [(bb, cfgobj.getint("BaubleHunt", "bauble_points_blue")), (bb+yb, cfgobj.getint("BaubleHunt", "bauble_points_yellow")), (bb+yb+rb, self._mv)] self._respawn = cfgobj.getboolean("BaubleHunt", "respawn_bauble_on_collect") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size")
def __init__(self, game, worldsize, objlistener=None): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not objlistener: initial listner callback function """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler(begin=self.__beginCollideObject, post_solve=self.__collideObject, separate=self.__endCollideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__influential = [] if objlistener == None: self.__objectListener = [] else: self.__objectListener = [objlistener] self.__toremove = [] self.__toadd = [] self.__active = False self.__started = False self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize))
def __init__(self, game, worldsize, pys=True): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler(begin=self.__beginCollideObject, post_solve=self.__collideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__planets = [] self.__nebulas = [] self.__objectListener = [] self.__toremove = [] self.__toadd = [] self.__active = True self.__pys = pys self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize)) if pys: logging.debug("Started gameloop for World %s", repr(self)) threading.Thread(None, self.__THREAD__gameloop, "WorldMap_gameloop_" + repr(self)).start()
def __init__(self, cfgobj): self._respawn = cfgobj.getboolean("BaubleHunt", "respawn_bauble_on_collect") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() self.__initialized = False super(BaubleHuntGame, self).__init__(cfgobj) # Find all High-Value Baubles once we've loaded that info from the BaseBaubleGame self._maxvalue = BaseBaubleGame.VALUE_TABLE[-1][1] for wobj in self.world: if isinstance(wobj, Bauble) and wobj.value == self._maxvalue: self.__baubles[wobj.id] = wobj self.__initialized = True self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size")
def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean("Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean("Game", "secondary_victory_highest") # TODO: Create Tournament Class self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tournamentinitialized = False if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self._tournamentnumgroups = cfgobj.getint("Tournament", "tournament_groups") self._tournamentgroups = [] self._tournamentcurrentgroup = 0 self._tournamentfinalgroup = [] self._tournamentfinalround = False self._tournamentfinalwinner = None self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = self.world_create() self.world.registerObjectListener(self.world_add_remove_object) if self.__autostart: self.round_start()
def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False self.__created = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean("Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean("Game", "secondary_victory_highest") self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tmanager = eval(cfgobj.get("Tournament", "manager"))(cfgobj, self.game_get_current_leader_list) if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = GameWorld(self, (cfgobj.getint("World","width"), cfgobj.getint("World","height")), self.world_add_remove_object) self.spawnmanager = SpawnManager(cfgobj, self.world) if self.__autostart: self.round_start()
def startGame(windowcaption, game, fullscreen=True, resolution=None, cfg=None, testcase=None): try: #region Initialization logging.info("Initiating PyGame...") world = game.world pygame.init() fpsClock = pygame.time.Clock() logging.debug("Initiating Screen...") if cfg.getboolean("Application", "sound"): logging.info("Starting Sound...") pygame.mixer.init() SCache(True) else: logging.info("Sound Disabled") SCache(False) ipaddress = getIPAddress() if resolution == None: resolution = detect_resolution(cfg) fullscreen = str2bool(fullscreen) if fullscreen in [True, False]: windowSurface = pygame.display.set_mode(resolution, (pygame.FULLSCREEN * fullscreen) | pygame.HWSURFACE | pygame.DOUBLEBUF) elif fullscreen == "window": os.environ['SDL_VIDEO_WINDOW_POS'] = '0,0' windowSurface = pygame.display.set_mode(resolution, pygame.NOFRAME | pygame.HWSURFACE | pygame.DOUBLEBUF) os.environ['SDL_VIDEO_WINDOW_POS'] = '' # Reset back else: raise ValueError("Invalid Fullscreen Windowing Mode") pygame.display.set_caption(windowcaption) logging.debug("Game GUI CFG...") # Let game initialize any GUI objects independently game.gui_initialize() colorBlack = pygame.Color(0, 0, 0) mt = 0 #shipw = ShipGUI(Ship((100, 100))) #shipw.ship.velocity.magnitude = 5 #region World Registration bgobjects = ThreadSafeDict() # items we want always in the background objects = ThreadSafeDict() trackplayer = None def addorremove(obj, added): logging.debug("GUI: Add/Remove Obj %s (%s) [%d]", repr(obj), repr(added), thread.get_ident()) try: if added: # TODO: #18 - Clean-up this by auto creating wrapper on name... if isinstance(obj, Ship): logging.debug("GUI: Adding Ship #%d", obj.id) objects[obj.id] = ShipGUI(obj, world) logging.debug("GUI: Added Ship #%d", obj.id) elif isinstance(obj, Nebula): logging.debug("GUI: Adding Nebula #%d", obj.id) bgobjects[obj.id] = NebulaGUI(obj, world) logging.debug("GUI: Added Nebula #%d", obj.id) elif isinstance(obj, Planet) or isinstance(obj, Constellation): logging.debug("GUI: Adding Planet #%d", obj.id) bgobjects[obj.id] = PlanetGUI(obj, world) logging.debug("GUI: Added Planet #%d", obj.id) elif isinstance(obj, WormHole): logging.debug("GUI: Adding WormHole #%d", obj.id) bgobjects[obj.id] = WormHoleGUI(obj, world) logging.debug("GUI: Added WormHole #%d", obj.id) elif isinstance(obj, Asteroid): logging.debug("GUI: Adding Asteroid #%d", obj.id) objects[obj.id] = AsteroidGUI(obj, world) logging.debug("GUI: Added Asteroid #%d", obj.id) elif isinstance(obj, Dragon): logging.debug("GUI: Adding Dragon #%d", obj.id) objects[obj.id] = DragonGUI(obj, world) logging.debug("GUI: Added Dragon #%d", obj.id) elif isinstance(obj, Torpedo): logging.debug("GUI: Adding Torpedo #%d", obj.id) objects[obj.id] = TorpedoGUI(obj, world) logging.debug("GUI: Added Torpedo #%d", obj.id) elif isinstance(obj, SpaceMine): logging.debug("GUI: Adding SpaceMine #%d", obj.id) objects[obj.id] = SpaceMineGUI(obj, world) logging.debug("GUI: Added SpaceMine #%d", obj.id) else: logging.debug("GUI: Adding %s #%d", repr(obj), obj.id) objects[obj.id] = obj.WRAPPERCLASS(obj, world) logging.debug("GUI: Added %s %d", repr(obj), obj.id) #eif else: if isinstance(obj, Ship): logging.debug("GUI: Ship #%d Dying", obj.id) ship = objects.get(obj.id, None) if ship != None: ship.dying = True logging.debug("GUI: Ship #%d Set Dying", obj.id) elif obj.id in objects: logging.debug("GUI: Removing Object #%d", obj.id) del objects[obj.id] logging.debug("GUI: Removed Object #%d", obj.id) elif obj.id in bgobjects: logging.debug("GUI: Removing BG Object #%d", obj.id) del bgobjects[obj.id] logging.debug("GUI: Removed BG Object #%d", obj.id) #eif #eif except: logging.error("GUI ERROR") logging.info(traceback.format_exc()) logging.error(traceback.format_exc()) print traceback.format_exc() #logging.debug("GUI: Add/Remove Done [%d]", thread.get_ident()) #endregion logging.debug("Register World Listener...") world.registerObjectListener(addorremove) logging.debug("Add Objects to GUI...") # Create objects for all items in world for obj in world: addorremove(obj, True) #efor # Create Stars #stars = [] #for i in xrange((world.width / STAR_DENSITY * world.height / STAR_DENSITY)): # stars.append(Star((random.randint(4, world.width - 4), random.randint(4, world.height - 4)))) logging.debug("Create Fonts...") # Start Main Loop font = pygame.font.Font("freesansbold.ttf", 12) bigfont = pygame.font.Font("freesansbold.ttf", 44) logging.debug("Load Textures...") spacetexture = Cache().getImage("Backgrounds/space") scalefactorx = float(world.size[0]) / resolution[0] scalefactory = float(world.size[1]) / resolution[1] offsetx = 0 maxoffsetx = -world.width + resolution[0] offsety = 0 maxoffsety = -world.height + resolution[1] def centerViewOnWorld(pos): return -pos[0] + resolution[0]/2, -pos[1] + resolution[1]/2 #keyboard repeat rate pygame.key.set_repeat(75, 25) zoomout = True mouselock = True MOUSELOCK_RANGE = resolution[0] / 8 logintercept = False mlh = MessageLogHandler(50) dynamiccamera = False mousemode = None prevzoom = zoomout showip = cfg.getboolean("Application", "showip") showplayerlist = cfg.getboolean("Application", "showstats") showroundtime = cfg.getboolean("Tournament", "tournament") tournamentinfo = cfg.getboolean("Tournament", "tournament") flags = {"DEBUG":False, "STATS":cfg.getboolean("Application", "showstats"), "NAMES":True, "GAME":cfg.getboolean("Application", "showstats"), "THREADS":False} logging.info("Create Main Surface...") #TODO: Optimize by only drawing objects in viewport... worldsurface = pygame.Surface((world.size[0], world.size[1]), flags=HWSURFACE) logging.debug("Game Start!") obj = None notexit = True # In test case we only care about test being done if testcase != None: notexit = False #endregion # Get spawnable items in alphabetical order SPAWN_TYPES = SpawnManager.ENTITY_TYPES.keys() SPAWN_TYPES.sort() while notexit or (testcase != None and not testcase.donetest): t = pygame.time.get_ticks() / 1000.0 #for star in stars: # star.draw(windowSurface) # TODO: figure out abstraction for pygame or method in order to get objwrapper to figure out if it needs # to draw in the screen or be no-op, ideally objwrapper just does what it does now... worldsurface.fill(colorBlack) #for x in xrange(offsetx, resolution[0], 512): # for y in xrange(offsety, resolution[1], 512): # worldsurface.blit(spacetexture, (x, y)) # draw background items first in specific z-order for obj in sorted(bgobjects.values(), key=attrgetter('zorder')): obj.draw(worldsurface, flags) if obj.dead: del bgobjects[obj._worldobj.id] # draw active objects for obj in objects: obj.draw(worldsurface, flags) if hasattr(obj._worldobj, "player") and obj._worldobj.player.sound != None: #print "Trying to play sound", obj._worldobj.player.sound SCache().play(obj._worldobj.player.sound) obj._worldobj.player.sound = None if obj.dead: del objects[obj._worldobj.id] if flags["GAME"]: game.gui_draw_game_world_info(worldsurface, flags, trackplayer) #region Key/Mouse Event Handling for event in pygame.event.get(): if event.type == QUIT: notexit = False elif (event.type == KEYDOWN and event.key == K_a) or (event.type == MOUSEBUTTONDOWN and event.button in (4, 5)): if mousemode == None or not mousemode.startswith("Add"): if event.type == KEYDOWN or event.button == 5: mousemode = "Add" + SPAWN_TYPES[0] else: mousemode = "Add" + SPAWN_TYPES[-1] elif event.type == KEYDOWN or event.button == 5: x = SPAWN_TYPES.index(mousemode[3:]) if x < len(SPAWN_TYPES) - 1: mousemode = "Add" + SPAWN_TYPES[x+1] elif event.type == MOUSEBUTTONDOWN: mousemode = "Add" + SPAWN_TYPES[0] else: mousemode = None else: x = SPAWN_TYPES.index(mousemode[3:]) if x > 0: mousemode = "Add" + SPAWN_TYPES[x-1] else: mousemode = "Add" + SPAWN_TYPES[-1] elif event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.event.post(pygame.event.Event(QUIT)) elif event.key == K_SPACE and not game.round_get_has_started(): game.round_start() elif event.key == K_d: flags["DEBUG"] = not flags["DEBUG"] elif event.key == K_i: showip = not showip elif event.key == K_p: showplayerlist = (showplayerlist + 1) % (2 + game.tournament_is_running()) elif event.key == K_s: flags["STATS"] = not flags["STATS"] elif event.key == K_n: flags["NAMES"] = not flags["NAMES"] elif event.key == K_UP: offsety += 16 elif event.key == K_DOWN: offsety -= 16 elif event.key == K_RIGHT: offsetx -= 16 elif event.key == K_LEFT: offsetx += 16 elif event.key == K_g: flags["GAME"] = not flags["GAME"] elif event.key == K_h: flags["THREADS"] = not flags["THREADS"] elif event.key == K_t and game.tournament_is_running(): tournamentinfo = not tournamentinfo elif event.key == K_z: zoomout = not zoomout elif event.key == K_m: mouselock = not mouselock elif event.key == K_PAGEUP and len(game.game_get_current_player_list()) > 0: if trackplayer == None: trackplayer = game.game_get_current_leader_list()[-1] else: lst = game.game_get_current_leader_list() for x in xrange(len(lst) - 1, -1, -1): if lst[x] == trackplayer: if x == 0: x = len(lst) trackplayer = lst[x-1] break elif event.key == K_PAGEDOWN and len(game.game_get_current_player_list()) > 0: if trackplayer == None: trackplayer = game.game_get_current_leader_list()[0] else: lst = game.game_get_current_leader_list() for x in xrange(len(lst)): if lst[x] == trackplayer: if x == len(lst) - 1: x = -1 trackplayer = lst[x+1] break elif event.key == K_END: trackplayer = None mlh.filter = None elif event.key == K_y: dynamiccamera = not dynamiccamera if not dynamiccamera: trackplayer = None elif event.key == K_l: logintercept = not logintercept logger = logging.getLogger() if logintercept: logger.addHandler(mlh) else: logger.removeHandler(mlh) mlh.messages.clear() elif event.key == K_k: if mousemode == "Destroy": mousemode = None else: mousemode = "Destroy" elif event.key == K_e: if mousemode == "Explode": mousemode = None else: mousemode = "Explode" elif event.key == K_v: if mousemode == "Move": mousemode = None zoomout = prevzoom else: mousemode = "Move" prevzoom = zoomout zoomout = True elif event.key == K_r: showroundtime = not showroundtime elif event.type == MOUSEBUTTONDOWN and event.button in (1, 2): #Left or Middle Click if zoomout: x = event.pos[0]*scalefactorx y = event.pos[1]*scalefactory else: x, y = event.pos[0]-offsetx, event.pos[1]-offsety #print zoomout, x, y if mousemode == "Move" and trackplayer != None: if event.button != 2: mousemode = None zoomout = prevzoom obj = trackplayer.object if obj != None: obj.body.position = (x, y) elif mousemode == "Destroy": if event.button != 2: mousemode = None v = Vec2d(x, y) for lst in objects, bgobjects: for obj in lst: if math.sqrt(obj._worldobj.body.position.get_dist_sqrd(v)) <= 32: logging.info("[GUI] Destroying Object #%d", obj._worldobj.id) if isinstance(obj, ShipGUI): obj._worldobj.killed = True world.remove(obj._worldobj) break elif mousemode == "Explode": if event.button != 2: mousemode = None world.causeExplosion((x, y), 256, 1000) elif mousemode != None and mousemode.startswith("Add"): game.spawnmanager.spawn_entity(mousemode[3:], (x, y), False) if event.button != 2: mousemode = None elif trackplayer != None: v = Vec2d(x, y) for obj in objects: if isinstance(obj, ShipGUI) and math.sqrt(obj._worldobj.body.position.get_dist_sqrd(v)) <= 32: logging.info("[GUI] Click Tracking Object #%d", obj._worldobj.id) trackplayer = obj._worldobj.player zoomout = False dynamiccamera = False break elif zoomout and event.button == 1: # zoom in zoomout = False #xoffset = -(((event.pos[0] - resolution[0]/2) % 16)*16 * scalefactorx) #yoffset = -(((event.pos[1] - resolution[1]/2) % 16)*16 * scalefactory) offsetx, offsety = centerViewOnWorld((event.pos[0]*scalefactorx, event.pos[1]*scalefactory)) else: # recenter on new click offsetx, offsety = centerViewOnWorld((event.pos[0]*scalefactorx, event.pos[1]*scalefactory)) elif event.type == MOUSEBUTTONDOWN and event.button == 3: #Right Click mousemode = None #endregion # Tracks a ship on screen if len(game.game_get_current_player_list()) == 0 or trackplayer not in game.game_get_current_leader_list(): trackplayer = None if dynamiccamera and len(game.game_get_current_leader_list()) > 0: trackplayer = game.game_get_current_leader_list()[0] if trackplayer != None: obj = trackplayer.object if obj != None: mlh.filter = "#" + repr(obj.id) offsetx, offsety = centerViewOnWorld(obj.body.position) # Pans world with mouse if not mouselock: # TODO Edge Detect pos = pygame.mouse.get_pos() if pos[0] < MOUSELOCK_RANGE: offsetx += 48 elif pos[0] > resolution[0] - MOUSELOCK_RANGE: offsetx -= 48 if pos[1] < MOUSELOCK_RANGE: offsety += 48 elif pos[1] > resolution[1] - MOUSELOCK_RANGE: offsety -= 48 #eif if offsetx > 0: offsetx = 0 if offsety > 0: offsety = 0 if offsetx < maxoffsetx: offsetx = maxoffsetx if offsety < maxoffsety: offsety = maxoffsety if zoomout: pygame.transform.scale(worldsurface, resolution, windowSurface) else: windowSurface.blit(worldsurface, (offsetx, offsety)) if showip: ip = bigfont.render(ipaddress, False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,0)) if not game.round_get_has_started(): ip = bigfont.render("Waiting For Start - Press Space", False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,resolution[1]/2-ip.get_height()/2)) if game.laststats != None: x = 0 for i in game.laststats: windowSurface.blit(font.render(i, False, (192, 192, 255)), (resolution[0]/2-300, resolution[1]/2 + 64 + 12*x)) x += 1 if showplayerlist > 0: x = 0 for i in game.gui_get_player_stats(all=(showplayerlist == 1)): c = (192, 192, 192) if trackplayer != None and trackplayer.name in i: c = trackplayer.color windowSurface.blit(font.render(i, False, c), (resolution[0]-300, 64 + 12*x)) x += 1 if showplayerlist == 1: windowSurface.blit(font.render(repr(x) + " Players Connected", False, (192, 192, 192)), (resolution[0]-300, 64 + 12 * x)) else: windowSurface.blit(font.render(repr(x) + " Players In Round", False, (192, 192, 192)), (resolution[0]-300, 64 + 12 * x)) if showroundtime and testcase == None: ip = bigfont.render(str(datetime.timedelta(seconds=game.round_get_time_remaining())), False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,32)) elif testcase != None: if testcase.flashcolor == False: testcase.flashcolor = (255, 255, 255) ip = bigfont.render(str(datetime.timedelta(milliseconds=pygame.time.get_ticks())), False, testcase.flashcolor) windowSurface.blit(ip, (32,32)) testcase.flashcolor = False if flags["DEBUG"]: mpos = pygame.mouse.get_pos() if zoomout: # TODO: Need to refactor and centralize these calculations as part of 'Camera' system x = mpos[0]*scalefactorx y = mpos[1]*scalefactory else: x, y = mpos[0]-offsetx, mpos[1]-offsety windowSurface.blit(font.render("DEBUG MODE", False, (255, 255, 255)), (resolution[0]/2-38,32)) windowSurface.blit(font.render("FPS: " + repr(fpsClock.get_fps()), False, (255, 255, 255)), (0,0)) windowSurface.blit(font.render("T: " + repr(len(threading.enumerate())), False, (192, 192, 192)), (0, 12)) windowSurface.blit(font.render("World Offset: " + repr((-offsetx, -offsety)), False, (255, 255, 255)), (0,24)) windowSurface.blit(font.render("World Size: " + repr(world.size) + " - " + repr((int(x), int(y))), False, (255, 255, 255)), (0,36)) windowSurface.blit(font.render("World Objects: " + repr(len(world)), False, (255, 255, 255)), (0,48)) if mouselock: windowSurface.blit(font.render("MOUSELOCK: ON", False, (255, 255, 255)), (resolution[0]-114,0)) else: windowSurface.blit(font.render("MOUSELOCK: OFF", False, (255, 255, 255)), (resolution[0]-114,0)) if "-" in windowcaption: text = font.render(windowcaption.split("-")[1], False, (192, 192, 192)) windowSurface.blit(text, (resolution[0]-text.get_width()-2, 12)) text = font.render(windowcaption.split("-")[0][18:-1], False, (192, 192, 192)) windowSurface.blit(text, (resolution[0]-text.get_width()-2, 24)) else: text = font.render(windowcaption[18:], False, (192, 192, 192)) windowSurface.blit(text, (resolution[0]-text.get_width()-2, 24)) windowSurface.blit(font.render(repr(flags), False, (255, 255, 255)), (0, resolution[1]-12)) if logintercept: for i in xrange(len(mlh.messages)): if i >= len(mlh.messages): break c = 145 + i*2 windowSurface.blit(font.render(mlh.messages[i], False, (c, c, c)), (10, 64 + 12*i)) if trackplayer == None: windowSurface.blit(font.render("Tracking Off", False, (255, 255, 255)), (resolution[0]/2-36,resolution[1]-12)) if trackplayer != None: to = trackplayer.object if to != None and objects.has_key(to.id): # if we're tracking a ship, let's print some useful info about it. #windowSurface.blit(font.render(trackplayer.name, False, trackplayer.color), (resolution[0] - 200, resolution[1] - 120)) windowSurface.fill((128, 128, 128), Rect(resolution[0] - 310, resolution[1] - 120, 300, 110), pygame.BLEND_ADD) pygame.draw.rect(windowSurface, (192, 192, 192, 128), Rect(resolution[0] - 310, resolution[1] - 120, 300, 110), 1) objects[to.id].draw(windowSurface, {"DEBUG":False, "STATS":False, "NAMES":True, "GAME":False}, Vec2d(resolution[0] - 250, resolution[1] - 64)) windowSurface.blit(font.render("%d" % to.body.velocity.length, False, (255, 255, 255)), (resolution[0] - 298, resolution[1] - 86)) netenergy = 4 x = 0 for cmd in to.commandQueue: netenergy -= cmd.energycost windowSurface.blit(font.render("%.1f (%s" % (cmd.energycost, repr(cmd)[14:]), False, trackplayer.color), (resolution[0] - 300, resolution[1] - 28 - x * 12)) x += 1 x = "+" if netenergy < 0: x = "-" #windowSurface.blit(infofont().render("Energy %d %s%.1f" % (to.energy.value, x, netenergy), False, trackplayer.color), (resolution[0] - 190, resolution[1] - 110)) # Energy Bar windowSurface.fill((0, 64, 0), Rect(resolution[0] - 190, resolution[1] - 114, 170, 18)) windowSurface.blit(pygame.transform.scale(Cache().getImage("HUD/Energy"), (166, 14)), (resolution[0] - 188, resolution[1] - 112), pygame.Rect(0, 0, 166 * to.energy.percent, 14)) windowSurface.blit(infofont().render("%d %s%.1f" % (to.energy.value, x, netenergy), False, (255, 255, 255)), (resolution[0] - 180, resolution[1] - 112)) # Shield Bar windowSurface.fill((0, 0, 96), Rect(resolution[0] - 190, resolution[1] - 94, 170, 8)) windowSurface.blit(pygame.transform.scale(Cache().getImage("HUD/Shield"), (166, 14)), (resolution[0] - 188, resolution[1] - 93), pygame.Rect(0, 0, 166 * to.shield.percent, 6)) windowSurface.blit(font.render(repr(int(100 * to.shield.percent)), False, (255, 255, 255)), (resolution[0] - 178, resolution[1] - 95)) # Health Bar windowSurface.fill((64, 0, 0), Rect(resolution[0] - 190, resolution[1] - 84, 170, 16)) windowSurface.blit(pygame.transform.scale(Cache().getImage("HUD/Health"), (166, 14)), (resolution[0] - 188, resolution[1] - 82), pygame.Rect(0, 0, 166 * to.health.percent, 12)) windowSurface.blit(infofont().render(repr(int(100 * to.health.percent)), False, (255, 255, 255)), (resolution[0] - 180, resolution[1] - 83)) if trackplayer.lastkilledby != None: windowSurface.blit(font.render("LD: " + trackplayer.lastkilledby, False, (255, 255, 255)), (resolution[0] - 298, resolution[1] - 118)) if flags["GAME"]: game.gui_draw_game_screen_info(windowSurface, flags, trackplayer) if tournamentinfo: game.gui_draw_tournament_bracket(windowSurface, flags, trackplayer) if trackplayer != None: n = font.render("Tracking: " + trackplayer.name, False, trackplayer.color) windowSurface.blit(n, (resolution[0]/2-n.get_width()/2,resolution[1]-12)) if mousemode == "Destroy": ip = bigfont.render("DESTROY OBJECT BY CLICK", False, (255, 0, 0)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode == "Explode": ip = bigfont.render("CLICK TO CAUSE EXPLOSION FORCE", False, (255, 0, 0)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode != None and mousemode[:3] == "Add": x = SPAWN_TYPES.index(mousemode[3:]) for i in xrange(x - 1, -1, -1): c = max(0, 128 + (i - x) * 16) ip = bigfont.render(SPAWN_TYPES[i], False, (c, c, c)) windowSurface.blit(ip, (resolution[0]/2 + 35, resolution[1]/2-24-((i - x) * -36))) ip = bigfont.render("Click to Add " + mousemode[3:], False, (255, 255, 0)) windowSurface.blit(ip, (resolution[0]/2-240, resolution[1]/2-ip.get_height()/2)) for i in xrange(x + 1, len(SPAWN_TYPES)): c = max(0, 128 - (i - x - 1) * 16) ip = bigfont.render(SPAWN_TYPES[i], False, (c, c, c)) windowSurface.blit(ip, (resolution[0]/2 + 35, resolution[1]/2+18+((i - x - 1) * 36))) if mousemode == "Move" and trackplayer == None: ip = bigfont.render("TRACK OBJECT BEFORE MOVING", False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode == "Move": ip = bigfont.render("CLICK TO MOVE "+trackplayer.name, False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) if flags["THREADS"]: st = mt+1 rt = st mt = 0 thrs = threading.enumerate() thrs.sort(key=lambda x: x.name, reverse=True) for thr in thrs: s = repr(thr) c = (192, 192, 192) ind = 30 if "Receiving" in s: i = rt rt += 1 c = (192, 192, 255) ind = resolution[0]/2 + 30 elif "Sending" in s: i = st st += 1 else: i = mt mt += 1 c = (255, 192, 192) windowSurface.blit(font.render(repr(thr), False, c), (ind, 64 + 12*i)) pygame.display.update() fpsClock.tick(30) # keep in sync with physics engine? #TODO: Add Logging??? # close out a game game.end() logging.info("Closing Pygame...") print "Closing Pygame" pygame.quit() except: print "EXCEPTION IN GUI" logging.exception("FATAL Error in GUI!!!") logging.error(traceback.format_exc()) print traceback.format_exc() world.gameerror = True # Flag for Unit Tests logging.info("GUI Closed")
class BasicGame(object): """ The BasicGame defines all notions of the basic mechanics of Space Battle as well as an infrastructure to build upon. The BasicGame provides information about a player's 'score', 'bestscore', 'deaths' by default. It also keeps track of the 'highscore' in a variety of manners and can run a tournament to advance players to a final round. Public Attributes: world: world object server: server object laststats: previous rounds scores Protected Attributes: _players: dictionary of players keyed by their network id (player.netid) _tournament: boolean True if playing tournament _tournamentinitialized: boolean True if tournament has been setup _highscore: current highscore of the game based on primary_victory_attr _leader: current leader of the game """ _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False self.__created = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean("Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean("Game", "secondary_victory_highest") self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tmanager = eval(cfgobj.get("Tournament", "manager"))(cfgobj, self.game_get_current_leader_list) if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = GameWorld(self, (cfgobj.getint("World","width"), cfgobj.getint("World","height")), self.world_add_remove_object) self.spawnmanager = SpawnManager(cfgobj, self.world) if self.__autostart: self.round_start() def end(self): """ Called when exiting GUI to terminate game. """ logging.info("Ending Game") self._tournament = True # force it to not restart timers again self.round_over() logging.info("Ending World") self.world.endGameLoop() #region Round/Timing Functions def _round_start_timer(self): """ Called automatically by round_start. """ if self.__roundtime > 0 and self._tournament: self.__timer = CallbackTimer(self.__roundtime, self.round_over) self.__timer.start() def round_get_time_remaining(self): """ Returns the amount of time remaining in a tournament round or zero """ if self.__timer == None: return 0 return self.__timer.time_left def round_start(self): """ Called by GUI client to start a synchronized game start, or automatically when the game is initialized if auto_start = true Called every time a new round starts or once if a tournament is not being played. """ if not self.__started and not self.world.gameerror: logging.info("Starting Game") self.__started = True self.__autostart = self.__allowafterstart if self._tournament: if not self._tmanager.is_initialized(): self._tmanager.initialize(self._players.values()) else: self._tmanager.next_round() if self._tmanager.is_final_round(): logging.info("[Tournament] Final Round") #eif #eif # we'll reset the world here so it's "fresh" and ready as soon as the game starts if self.__resetworldround or not self.__created: logging.info("Resetting World.") self.world_create() #eif if not self.__created and not self.world.gameerror: self.world.start() self.__created = True self.spawnmanager.start() # want spawn manager here so it can spawn items on players for player in self.game_get_current_player_list(): self._game_add_ship_for_player(player.netid, roundstart=True) #next self._round_start_timer() def player_added(self, player, reason): """ player_added is called in three separate cases 0) When the server first registers a ship on the server and adds them to the game 1) When a round starts and a player is added to the game round 2) When a player dies and is respawned in the world. cases 0 and 1 may occur one after another if auto_start is set to true. The ship object will have been created, but not added to the physics world yet, so no callback to world_add_remove_object may have occured yet. This is also before any commands are requested for the player (i.e. before the first environment is sent). You should add and initialize any new properties that you want on a player here. Reason values: _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 Analog to player_died """ logging.info("Player %s Added(%d)", player.name, reason) if reason == 1: player.score = self._points_initial player.bestscore = self._points_initial player.deaths = 0 self.spawnmanager.player_added(reason) def player_get_start_position(self): """ Should return a position for a player to start at in the world. """ pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) x = 0 while len(self.world.getObjectsInArea(pos, 150)) > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) return pos def round_over(self): """ Called in a tournament when the round time expires. Will automatically advance the top players to the next round. """ if self.__timer != None: self.__timer.cancel() # if we get a round-over prematurely or do to some other condition, we should cancel this current timer self.__timer = None self.spawnmanager.stop() logging.debug("[Game] Round Over") self.game_update(0) # make sure we do one last update to sync and refresh the leaderboard cache # Get Stats for only the players in the round self.laststats = self.gui_get_player_stats() logging.info("[Game] Stats: %s", repr(self.laststats)) if len(self.laststats) > 0: self._tmanager.check_results(self.__leaderboard_cache, self.laststats) #eif for player in self._players: player.roundover = True if player.object != None: player.object.destroyed = True self.world.remove(player.object) # here we're forcibly removing them as we're clearing the game if self.__resetworldround: logging.info("Destroying World") self.world.destroy_all() # overwritten by allowafterstart self.__autostart = self.cfg.getboolean("Game", "auto_start") # TODO: figure out a better way to tie these together if self._tournament: self.__autostart = False # If not autostart, wait for next round to start self.__started = False if self.__autostart: self.round_start() def world_create(self): """ Called by game at start of round to create world and when world reset, defaults to the standard world configuration from the config file definitions. """ self.spawnmanager.spawn_initial() def round_get_has_started(self): """ Returns True when game has started. """ return self.__started def game_update(self, t): """ Called by the World to notify the passage of time The default game uses this update to cache the current leaderboard to be used by the GUI and the game to determine the leader info to send out. Note: This is called from the core game loop, best not to delay much here """ self.__leaderboard_cache = sorted(sorted(self.game_get_current_player_list(), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) if len(self.__leaderboard_cache) > 0: self._leader = self.__leaderboard_cache[0] self._highscore = getattr(self._leader, self._primary_victory) #eif #endregion #region Network driven functions def server_register_player(self, name, color, imgindex, netid, aiship=None): """ Called by the server when a new client registration message is received Has all information pertaining to a player entity Parameters: ship: Ship object - used to register AI Ships to the game world """ if not self._players.has_key(netid): if (self.__started and self.__allowafterstart) or not self.__started: # create new player # p = Player(name, color, imgindex, netid, self._primary_victory_high) self.player_added(p, BasicGame._ADD_REASON_REGISTER_) self._players[netid] = p if aiship != None: self.__aiships[netid] = aiship if self.__autostart: self._game_add_ship_for_player(netid, roundstart=True) logging.info("Registering Player: %s %d", name, netid) return True return False def server_process_network_message(self, ship, cmdname, cmddict={}): """ Called by the server when it doesn't know how to convert a network message into a Ship Command. This function is used to create commands custom to the game itself. Parameters: ship: Ship object which will process the Command cmdname: string network shortcode for command, set overriding getName in ShipCommand on Client cmddict: dictionary of properties of a command, set using private fields on ShipCommand class Return: return a server 'Command' object, a string indicating an error, or None """ pass def server_process_command(self, ship, command): """ Called by the server after a Command object has been formed from a network message. This includes the command processed by a game in server_process_network_message. This function would allow the game to interact with the built-in commands. Parameters: ship: Ship object to process this command command: Command object that will be added to the ship's computer by the server Return: This method should return the command or a string indicating an error. Returning None would cause a silent failure on the client. In the case of a string, the message would be printed out in the client console. In both error cases, another standard request for a command is made on the client. """ return command def _game_add_ship_for_player(self, netid, roundstart=False): """ Called internally when a player registers if autostart is true, or when round_start is called Also called when a player respawns. Look to override player_added instead. Also, sends initial environment to start RPC loop with client """ if netid < -2: # AI Ship self._players[netid].object = self.__aiships[netid] # reset AI Ship to look like new ship, though reuse object # TODO: this doesn't work, need 'new' object to add to world, or queue the add to occur after the remove...boo self.__aiships[netid].health.full() self.__aiships[netid].energy.full() self.__aiships[netid].shield.full() self.__aiships[netid].destroyed = False self.__aiships[netid].killed = False self.__aiships[netid].timealive = 0 self.__aiships[netid].body.velocity = Vec2d(0, 0) if not roundstart: # ai ship will be initialized with a starting position for round entry, but if killed, will randomize self.__aiships[netid].body.position = self.player_get_start_position() self._players[netid].object.ship_added() # tell AI ship to start else: self._players[netid].object = Ship(self.player_get_start_position(), self.world) logging.info("Adding Ship for Player %d id #%d with Name %s", netid, self._players[netid].object.id, self._players[netid].name) self._players[netid].object.player = self._players[netid] self._players[netid].object.owner = self._players[netid].object # Make ships owners of themselves for easier logic with ownership? self._players[netid].roundover = False if not self.cfg.getboolean("World", "collisions"): self._players[netid].object.shape.group = 1 if roundstart: self.player_added(self._players[netid], BasicGame._ADD_REASON_START_) else: self.player_added(self._players[netid], BasicGame._ADD_REASON_RESPAWN_) #eif self.world.append(self._players[netid].object) logging.info("Sending New Ship #%d Environment Info", self._players[netid].object.id) if netid >= 0 and not self._players[netid].waiting: self.server.sendEnvironment(self._players[netid].object, netid) return self._players[netid].object def game_get_info(self): """ Called when a client is connected and appends this data to the default image number, world width and world height Should at least return a key "GAMENAME" with the name of the game module. Your game name should be "Basic" if you don't want to return anything in game_get_extra_environment to the player besides Score, Bestscore, Deaths, Highscore, Time left, and Round time. Note: The client doesn't expose a way to get anything besides the game name right now. """ return {"GAMENAME": "Basic"} def game_get_extra_environment(self, player): """ Called by World to return extra environment info to a player when they need to return a command (for just that player). If you desire to return more information to the client, you can create an extended BasicGameInfo class there and repackage the jar. See info in the server docs on adding a subgame. """ return {"SCORE": player.score, "BESTSCORE": player.bestscore, "DEATHS": player.deaths, "HIGHSCORE": self._highscore, "TIMELEFT": int(self.round_get_time_remaining()), "ROUNDTIME": self.__roundtime, "LSTDSTRBY": player.lastkilledby} def game_get_extra_radar_info(self, obj, objdata, player): """ Called by the World when the obj is being radared, should add new properties to the objdata. Note: it is not supported to pick up these values directly by the client, it is best to use the existing properties that make sense. """ if hasattr(obj, "player") and self.cfg.getboolean("World", "radar_include_name"): objdata["NAME"] = obj.player.name def server_disconnect_player(self, netid): """ Called by the server when a disconnect message is received by a player's client player_died should also be called, so it is recommended to override that method instead. """ for player in self._players: if player.netid == netid and player.object != None: logging.debug("Player %s Disconnected", player.name) player.disconnected = True player.object.killed = True # terminate self.world.remove(player.object) player.object = None if self.__allowreentry: if self._players.has_key(player.netid): del self._players[player.netid] # should be handled by world part return True logging.debug("Couldn't find player with netid %d to disconnect", netid); return False def player_get_by_name(self, name): """ Retrieves a player by their name. """ for player in self._players.values(): if player.name == name: return player return None def player_get_by_network_id(self, netid): """ Used by the server to retrieve a player by their network id. """ if self._players.has_key(netid): return self._players[netid] return None #endregion #region Player Scoring / Leaderboard Functions def player_died(self, player, gone): """ Will be called when a player dies, will adjust score appropriately based on game rules. Called before player object removed from player. gone will be True if the player is disconnected or killed (they won't respawn) Analog to player_added """ if not player.roundover: # only count deaths during the round! logging.info("Player %s Died", player.name) player.deaths += 1 if self._points_lost_on_death > 0: player.update_score(-self._points_lost_on_death) if self._primary_victory == "bestscore": # we need to subtract/add to bestscore in stead if self._primary_victory_high: player.bestscore -= self._points_lost_on_death if player.bestscore < 0: player.bestscore = 0 else: player.bestscore += self._points_lost_on_death if player.bestscore > self._points_initial: player.bestscore = self._points_initial if self._reset_score_on_death: self._player_reset_score(player) def _player_reset_score(self, player): """ Used to reset a players score and determine the new leader. Will be called by the default implementation of player_died if the reset_score_on_death configuration property is true. """ player.score = self._points_initial def game_get_current_leader_list(self, all=False): """ Gets the list of players sorted by their score (highest first) (or all players) """ # secondary victory first, primary second if all: return sorted(sorted(self.game_get_current_player_list(all), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) # TODO: Cache this and regen, update leader and highscore value there too, should I do this once every game update? return self.__leaderboard_cache def game_get_current_player_list(self, all=False): """ Returns a list of player objects for players in the current round Returns all players if no tournament running or requested """ if all or not self._tournament: return self._players else: return self._tmanager.get_players_in_round() #eif def player_get_stat_string(self, player): """ Should return a string with information about the player and their current score. Defaults to: primary_score secondary_score : player_name """ return "%.1f" % getattr(player, self._primary_victory) + " " + str(getattr(player, self._secondary_victory)) + " : " + player.name def tournament_is_running(self): """ Returns true if a tournament is running. """ return self._tournament #endregion #region World/Collision Functions def world_add_remove_object(self, wobj, added): """ Called by world when an object is added or destroyed (before added (guaranteed to not have update) and after removed (though may receive last update)) For simple tasks involving players look to the player_died or player_added methods killed ships will not return (used to prevent respawn) """ logging.debug("[Game] Add Object(%s): #%d (%s)", repr(added), wobj.id, friendly_type(wobj)) if not added and isinstance(wobj, SpaceMine) and wobj.active: self.world.causeExplosion(wobj.body.position, SpaceMine.RADIUS, SpaceMine.FORCE, True) # TODO: Cause splash damage? if not added and isinstance(wobj, Ship) and wobj.player.netid in self._players: nid = wobj.player.netid # if we were given an expiration time, means we haven't issued a command, so kill the ship if wobj.has_expired() and self.cfg.getboolean("Server", "disconnect_on_idle"): logging.info("Ship #%d killed due to timeout.", wobj.id) wobj.killed = True if hasattr(wobj, "killedby") and wobj.killedby != None: if isinstance(wobj.killedby, Ship): self._players[nid].lastkilledby = wobj.killedby.player.name else: self._players[nid].lastkilledby = friendly_type(wobj.killedby) + " #" + str(wobj.killedby.id) self.player_died(self._players[nid], (self._players[nid].disconnected or wobj.killed)) self._players[nid].object = None if not self._players[nid].disconnected: if self._disconnect_on_death or wobj.killed: if self.__allowreentry: del self._players[nid] # TODO: disconnect AI? if nid >= 0: self.server.sendDisconnect(nid) else: if not self._players[nid].roundover: # if the round isn't over, then re-add the ship self._game_add_ship_for_player(nid) if not added: self.spawnmanager.check_number(wobj) def world_physics_pre_collision(self, obj1, obj2): """ Called by the physics engine when two objects just touch for the first time return [True/False, [func, obj, para...]... ] use False to not process collision in the physics engine, the function callback will still be called return a list with lists of function callback requests for the function, and object, and extra parameters The default game prevents anything from colliding with (BlackHole, Nebula, or Star) collide returns False. """ logging.debug("Object #%d colliding with #%d", obj1.id, obj2.id) return obj1.collide_start(obj2) and obj2.collide_start(obj1) def world_physics_collision(self, obj1, obj2, damage): """ Called by the physics engine when two objects collide Return [[func, obj, parameter]...] The default game handles inflicting damage on entities in this step. It is best to override world_physics_pre_collision if you want to prevent things from occuring in the first place. """ logging.debug("Object #%d collided with #%d for %f damage", obj1.id, obj2.id, damage) r = [] obj1.take_damage(damage, obj2) obj2.take_damage(damage, obj1) for gobj in (obj1, obj2): # check both objects for callback if gobj.health.maximum > 0 and gobj.health.value <= 0: logging.info("Object #%d destroyed by %s", gobj.id, repr(gobj.killedby)) r.append([self.world_physics_post_collision, gobj, damage]) #eif if r == []: return None return r def world_physics_post_collision(self, dobj, damage): """ Setup and called by world_physics_collision to process objects which have been destroyed as a result of taking too much damage. The default game causes an explosion force based on the strength of the collision in the vicinity of the collision. dobj: the object destroyed para: extra parameters from a previous step, by default collision passes the strength of the collision only """ strength = damage logging.info("Destroying Object: #%d, Force: %d [%d]", dobj.id, strength, thread.get_ident()) dobj.destroyed = True # get rid of object self.world.causeExplosion(dobj.body.position, dobj.radius * 5, strength, True) #Force in physics step def world_physics_end_collision(self, obj1, obj2): """ Called by the physics engine after two objects stop overlapping/colliding. This is still called even if the pre_collision returned 'False' and no actual collision was processed """ logging.debug("Object #%d no longer colliding with #%d", obj1.id, obj2.id) # notify each object of the finalization of the collision obj1.collide_end(obj2) obj2.collide_end(obj1) #endregion #region GUI Drawing def gui_initialize(self): """ Used to initialize GUI resources at the appropriate time after the graphics engine has been initialized. """ self._tmanager.gui_initialize() self._dfont = debugfont() def gui_draw_game_world_info(self, surface, flags, trackplayer): """ Called by GUI to have the game draw additional (optional) info in the world when toggled on 'G'. (coordinates related to the game world) """ pass def gui_draw_game_screen_info(self, screen, flags, trackplayer): """ Called by GUI to have the game draw additional (optional) info on screen when toggled on 'G'. (coordinates related to the screen) """ pass def gui_get_player_stats(self, all=False): """ Called by GUI to get the sorted list of player stats. GUI expects a list of strings, you should usually override player_get_stat_string. """ sstat = [] for player in self.game_get_current_leader_list(all): sstat.append(self.player_get_stat_string(player)) return sstat def gui_draw_tournament_bracket(self, screen, flags, trackplayer): """ Called by GUI to draw info about the round/tournament (optional) when toggled on 'T'. (coordinates related to the screen) """ if self._tournament and self._tmanager.is_initialized(): self._tmanager.gui_draw_tournament_bracket(screen, flags, trackplayer)
class BasicGame(object): """ The BasicGame defines all notions of the basic mechanics of Space Battle as well as an infrastructure to build upon. The BasicGame provides information about a player's 'score', 'bestscore', 'deaths' by default. It also keeps track of the 'highscore' in a variety of manners and can run a tournament to advance players to a final round. Public Attributes: world: world object server: server object laststats: previous rounds scores Protected Attributes: _players: dictionary of players keyed by their network id (player.netid) _tournament: boolean True if playing tournament _tournamentinitialized: boolean True if tournament has been setup _highscore: current highscore of the game based on primary_victory_attr _leader: current leader of the game """ _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False self.__created = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean( "Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean( "Game", "secondary_victory_highest") self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tmanager = eval(cfgobj.get("Tournament", "manager"))( cfgobj, self.game_get_current_leader_list) if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = GameWorld(self, (cfgobj.getint( "World", "width"), cfgobj.getint("World", "height")), self.world_add_remove_object) self.spawnmanager = SpawnManager(cfgobj, self.world) if self.__autostart: self.round_start() def end(self): """ Called when exiting GUI to terminate game. """ logging.info("Ending Game") self._tournament = True # force it to not restart timers again self.round_over() logging.info("Ending World") self.world.endGameLoop() #region Round/Timing Functions def _round_start_timer(self): """ Called automatically by round_start. """ if self.__roundtime > 0 and self._tournament: self.__timer = CallbackTimer(self.__roundtime, self.round_over) self.__timer.start() def round_get_time_remaining(self): """ Returns the amount of time remaining in a tournament round or zero """ if self.__timer == None: return 0 return self.__timer.time_left def round_start(self): """ Called by GUI client to start a synchronized game start, or automatically when the game is initialized if auto_start = true Called every time a new round starts or once if a tournament is not being played. """ if not self.__started and not self.world.gameerror: logging.info("Starting Game") self.__started = True self.__autostart = self.__allowafterstart if self._tournament: if not self._tmanager.is_initialized(): self._tmanager.initialize(self._players.values()) else: self._tmanager.next_round() if self._tmanager.is_final_round(): logging.info("[Tournament] Final Round") #eif #eif # we'll reset the world here so it's "fresh" and ready as soon as the game starts if self.__resetworldround or not self.__created: logging.info("Resetting World.") self.world_create() #eif if not self.__created and not self.world.gameerror: self.world.start() self.__created = True self.spawnmanager.start( ) # want spawn manager here so it can spawn items on players for player in self.game_get_current_player_list(): self._game_add_ship_for_player(player.netid, roundstart=True) #next self._round_start_timer() def player_added(self, player, reason): """ player_added is called in three separate cases 0) When the server first registers a ship on the server and adds them to the game 1) When a round starts and a player is added to the game round 2) When a player dies and is respawned in the world. cases 0 and 1 may occur one after another if auto_start is set to true. The ship object will have been created, but not added to the physics world yet, so no callback to world_add_remove_object may have occured yet. This is also before any commands are requested for the player (i.e. before the first environment is sent). You should add and initialize any new properties that you want on a player here. Reason values: _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 Analog to player_died """ logging.info("Player %s Added(%d)", player.name, reason) if reason == 1: player.score = self._points_initial player.bestscore = self._points_initial player.deaths = 0 self.spawnmanager.player_added(reason) def player_get_start_position(self): """ Should return a position for a player to start at in the world. """ pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) x = 0 while len(self.world.getObjectsInArea(pos, 150)) > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) return pos def round_over(self): """ Called in a tournament when the round time expires. Will automatically advance the top players to the next round. """ if self.__timer != None: self.__timer.cancel( ) # if we get a round-over prematurely or do to some other condition, we should cancel this current timer self.__timer = None self.spawnmanager.stop() logging.debug("[Game] Round Over") self.game_update( 0 ) # make sure we do one last update to sync and refresh the leaderboard cache # Get Stats for only the players in the round self.laststats = self.gui_get_player_stats() logging.info("[Game] Stats: %s", repr(self.laststats)) if len(self.laststats) > 0: self._tmanager.check_results(self.__leaderboard_cache, self.laststats) #eif for player in self._players: player.roundover = True if player.object != None: player.object.destroyed = True self.world.remove( player.object ) # here we're forcibly removing them as we're clearing the game if self.__resetworldround: logging.info("Destroying World") self.world.destroy_all() # overwritten by allowafterstart self.__autostart = self.cfg.getboolean("Game", "auto_start") # TODO: figure out a better way to tie these together if self._tournament: self.__autostart = False # If not autostart, wait for next round to start self.__started = False if self.__autostart: self.round_start() def world_create(self): """ Called by game at start of round to create world and when world reset, defaults to the standard world configuration from the config file definitions. """ self.spawnmanager.spawn_initial() def round_get_has_started(self): """ Returns True when game has started. """ return self.__started def game_update(self, t): """ Called by the World to notify the passage of time The default game uses this update to cache the current leaderboard to be used by the GUI and the game to determine the leader info to send out. Note: This is called from the core game loop, best not to delay much here """ self.__leaderboard_cache = sorted( sorted(self.game_get_current_player_list(), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) if len(self.__leaderboard_cache) > 0: self._leader = self.__leaderboard_cache[0] self._highscore = getattr(self._leader, self._primary_victory) #eif #endregion #region Network driven functions def server_register_player(self, name, color, imgindex, netid, aiship=None): """ Called by the server when a new client registration message is received Has all information pertaining to a player entity Parameters: ship: Ship object - used to register AI Ships to the game world """ if not self._players.has_key(netid): if (self.__started and self.__allowafterstart) or not self.__started: # create new player # p = Player(name, color, imgindex, netid, self._primary_victory_high) self.player_added(p, BasicGame._ADD_REASON_REGISTER_) self._players[netid] = p if aiship != None: self.__aiships[netid] = aiship if self.__autostart: self._game_add_ship_for_player(netid, roundstart=True) logging.info("Registering Player: %s %d", name, netid) return True return False def server_process_network_message(self, ship, cmdname, cmddict={}): """ Called by the server when it doesn't know how to convert a network message into a Ship Command. This function is used to create commands custom to the game itself. Parameters: ship: Ship object which will process the Command cmdname: string network shortcode for command, set overriding getName in ShipCommand on Client cmddict: dictionary of properties of a command, set using private fields on ShipCommand class Return: return a server 'Command' object, a string indicating an error, or None """ pass def server_process_command(self, ship, command): """ Called by the server after a Command object has been formed from a network message. This includes the command processed by a game in server_process_network_message. This function would allow the game to interact with the built-in commands. Parameters: ship: Ship object to process this command command: Command object that will be added to the ship's computer by the server Return: This method should return the command or a string indicating an error. Returning None would cause a silent failure on the client. In the case of a string, the message would be printed out in the client console. In both error cases, another standard request for a command is made on the client. """ return command def _game_add_ship_for_player(self, netid, roundstart=False): """ Called internally when a player registers if autostart is true, or when round_start is called Also called when a player respawns. Look to override player_added instead. Also, sends initial environment to start RPC loop with client """ if netid < -2: # AI Ship self._players[netid].object = self.__aiships[netid] # reset AI Ship to look like new ship, though reuse object # TODO: this doesn't work, need 'new' object to add to world, or queue the add to occur after the remove...boo self.__aiships[netid].health.full() self.__aiships[netid].energy.full() self.__aiships[netid].shield.full() self.__aiships[netid].destroyed = False self.__aiships[netid].killed = False self.__aiships[netid].timealive = 0 self.__aiships[netid].body.velocity = Vec2d(0, 0) if not roundstart: # ai ship will be initialized with a starting position for round entry, but if killed, will randomize self.__aiships[ netid].body.position = self.player_get_start_position() self._players[netid].object.ship_added() # tell AI ship to start else: self._players[netid].object = Ship( self.player_get_start_position(), self.world) logging.info("Adding Ship for Player %d id #%d with Name %s", netid, self._players[netid].object.id, self._players[netid].name) self._players[netid].object.player = self._players[netid] self._players[netid].object.owner = self._players[ netid].object # Make ships owners of themselves for easier logic with ownership? self._players[netid].roundover = False if not self.cfg.getboolean("World", "collisions"): self._players[netid].object.shape.group = 1 if roundstart: self.player_added(self._players[netid], BasicGame._ADD_REASON_START_) else: self.player_added(self._players[netid], BasicGame._ADD_REASON_RESPAWN_) #eif self.world.append(self._players[netid].object) logging.info("Sending New Ship #%d Environment Info", self._players[netid].object.id) if netid >= 0 and not self._players[netid].waiting: self.server.sendEnvironment(self._players[netid].object, netid) return self._players[netid].object def game_get_info(self): """ Called when a client is connected and appends this data to the default image number, world width and world height Should at least return a key "GAMENAME" with the name of the game module. Your game name should be "Basic" if you don't want to return anything in game_get_extra_environment to the player besides Score, Bestscore, Deaths, Highscore, Time left, and Round time. Note: The client doesn't expose a way to get anything besides the game name right now. """ return {"GAMENAME": "Basic"} def game_get_extra_environment(self, player): """ Called by World to return extra environment info to a player when they need to return a command (for just that player). If you desire to return more information to the client, you can create an extended BasicGameInfo class there and repackage the jar. See info in the server docs on adding a subgame. """ return { "SCORE": player.score, "BESTSCORE": player.bestscore, "DEATHS": player.deaths, "HIGHSCORE": self._highscore, "TIMELEFT": int(self.round_get_time_remaining()), "ROUNDTIME": self.__roundtime, "LSTDSTRBY": player.lastkilledby } def game_get_extra_radar_info(self, obj, objdata, player): """ Called by the World when the obj is being radared, should add new properties to the objdata. Note: it is not supported to pick up these values directly by the client, it is best to use the existing properties that make sense. """ if hasattr(obj, "player") and self.cfg.getboolean( "World", "radar_include_name"): objdata["NAME"] = obj.player.name def server_disconnect_player(self, netid): """ Called by the server when a disconnect message is received by a player's client player_died should also be called, so it is recommended to override that method instead. """ for player in self._players: if player.netid == netid and player.object != None: logging.debug("Player %s Disconnected", player.name) player.disconnected = True player.object.killed = True # terminate self.world.remove(player.object) player.object = None if self.__allowreentry: if self._players.has_key(player.netid): del self._players[ player.netid] # should be handled by world part return True logging.debug("Couldn't find player with netid %d to disconnect", netid) return False def player_get_by_name(self, name): """ Retrieves a player by their name. """ for player in self._players.values(): if player.name == name: return player return None def player_get_by_network_id(self, netid): """ Used by the server to retrieve a player by their network id. """ if self._players.has_key(netid): return self._players[netid] return None #endregion #region Player Scoring / Leaderboard Functions def player_died(self, player, gone): """ Will be called when a player dies, will adjust score appropriately based on game rules. Called before player object removed from player. gone will be True if the player is disconnected or killed (they won't respawn) Analog to player_added """ if not player.roundover: # only count deaths during the round! logging.info("Player %s Died", player.name) player.deaths += 1 if self._points_lost_on_death > 0: player.update_score(-self._points_lost_on_death) if self._primary_victory == "bestscore": # we need to subtract/add to bestscore in stead if self._primary_victory_high: player.bestscore -= self._points_lost_on_death if player.bestscore < 0: player.bestscore = 0 else: player.bestscore += self._points_lost_on_death if player.bestscore > self._points_initial: player.bestscore = self._points_initial if self._reset_score_on_death: self._player_reset_score(player) def _player_reset_score(self, player): """ Used to reset a players score and determine the new leader. Will be called by the default implementation of player_died if the reset_score_on_death configuration property is true. """ player.score = self._points_initial def game_get_current_leader_list(self, all=False): """ Gets the list of players sorted by their score (highest first) (or all players) """ # secondary victory first, primary second if all: return sorted(sorted(self.game_get_current_player_list(all), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) # TODO: Cache this and regen, update leader and highscore value there too, should I do this once every game update? return self.__leaderboard_cache def game_get_current_player_list(self, all=False): """ Returns a list of player objects for players in the current round Returns all players if no tournament running or requested """ if all or not self._tournament: return self._players else: return self._tmanager.get_players_in_round() #eif def player_get_stat_string(self, player): """ Should return a string with information about the player and their current score. Defaults to: primary_score secondary_score : player_name """ return "%.1f" % getattr(player, self._primary_victory) + " " + str( getattr(player, self._secondary_victory)) + " : " + player.name def tournament_is_running(self): """ Returns true if a tournament is running. """ return self._tournament #endregion #region World/Collision Functions def world_add_remove_object(self, wobj, added): """ Called by world when an object is added or destroyed (before added (guaranteed to not have update) and after removed (though may receive last update)) For simple tasks involving players look to the player_died or player_added methods killed ships will not return (used to prevent respawn) """ logging.debug("[Game] Add Object(%s): #%d (%s)", repr(added), wobj.id, friendly_type(wobj)) if not added and isinstance(wobj, SpaceMine) and wobj.active: self.world.causeExplosion(wobj.body.position, SpaceMine.RADIUS, SpaceMine.FORCE, True) # TODO: Cause splash damage? if not added and isinstance( wobj, Ship) and wobj.player.netid in self._players: nid = wobj.player.netid # if we were given an expiration time, means we haven't issued a command, so kill the ship if wobj.has_expired() and self.cfg.getboolean( "Server", "disconnect_on_idle"): logging.info("Ship #%d killed due to timeout.", wobj.id) wobj.killed = True if hasattr(wobj, "killedby") and wobj.killedby != None: if isinstance(wobj.killedby, Ship): self._players[nid].lastkilledby = wobj.killedby.player.name else: self._players[nid].lastkilledby = friendly_type( wobj.killedby) + " #" + str(wobj.killedby.id) self.player_died(self._players[nid], (self._players[nid].disconnected or wobj.killed)) self._players[nid].object = None if not self._players[nid].disconnected: if self._disconnect_on_death or wobj.killed: if self.__allowreentry: del self._players[nid] # TODO: disconnect AI? if nid >= 0: self.server.sendDisconnect(nid) else: if not self._players[nid].roundover: # if the round isn't over, then re-add the ship self._game_add_ship_for_player(nid) if not added: self.spawnmanager.check_number(wobj) def world_physics_pre_collision(self, obj1, obj2): """ Called by the physics engine when two objects just touch for the first time return [True/False, [func, obj, para...]... ] use False to not process collision in the physics engine, the function callback will still be called return a list with lists of function callback requests for the function, and object, and extra parameters The default game prevents anything from colliding with (BlackHole, Nebula, or Star) collide returns False. """ logging.debug("Object #%d colliding with #%d", obj1.id, obj2.id) return obj1.collide_start(obj2) and obj2.collide_start(obj1) def world_physics_collision(self, obj1, obj2, damage): """ Called by the physics engine when two objects collide Return [[func, obj, parameter]...] The default game handles inflicting damage on entities in this step. It is best to override world_physics_pre_collision if you want to prevent things from occuring in the first place. """ logging.debug("Object #%d collided with #%d for %f damage", obj1.id, obj2.id, damage) r = [] obj1.take_damage(damage, obj2) obj2.take_damage(damage, obj1) for gobj in (obj1, obj2): # check both objects for callback if gobj.health.maximum > 0 and gobj.health.value <= 0: logging.info("Object #%d destroyed by %s", gobj.id, repr(gobj.killedby)) r.append([self.world_physics_post_collision, gobj, damage]) #eif if r == []: return None return r def world_physics_post_collision(self, dobj, damage): """ Setup and called by world_physics_collision to process objects which have been destroyed as a result of taking too much damage. The default game causes an explosion force based on the strength of the collision in the vicinity of the collision. dobj: the object destroyed para: extra parameters from a previous step, by default collision passes the strength of the collision only """ strength = damage logging.info("Destroying Object: #%d, Force: %d [%d]", dobj.id, strength, thread.get_ident()) dobj.destroyed = True # get rid of object self.world.causeExplosion(dobj.body.position, dobj.radius * 5, strength, True) #Force in physics step def world_physics_end_collision(self, obj1, obj2): """ Called by the physics engine after two objects stop overlapping/colliding. This is still called even if the pre_collision returned 'False' and no actual collision was processed """ logging.debug("Object #%d no longer colliding with #%d", obj1.id, obj2.id) # notify each object of the finalization of the collision obj1.collide_end(obj2) obj2.collide_end(obj1) #endregion #region GUI Drawing def gui_initialize(self): """ Used to initialize GUI resources at the appropriate time after the graphics engine has been initialized. """ self._tmanager.gui_initialize() self._dfont = debugfont() def gui_draw_game_world_info(self, surface, flags, trackplayer): """ Called by GUI to have the game draw additional (optional) info in the world when toggled on 'G'. (coordinates related to the game world) """ pass def gui_draw_game_screen_info(self, screen, flags, trackplayer): """ Called by GUI to have the game draw additional (optional) info on screen when toggled on 'G'. (coordinates related to the screen) """ pass def gui_get_player_stats(self, all=False): """ Called by GUI to get the sorted list of player stats. GUI expects a list of strings, you should usually override player_get_stat_string. """ sstat = [] for player in self.game_get_current_leader_list(all): sstat.append(self.player_get_stat_string(player)) return sstat def gui_draw_tournament_bracket(self, screen, flags, trackplayer): """ Called by GUI to draw info about the round/tournament (optional) when toggled on 'T'. (coordinates related to the screen) """ if self._tournament and self._tmanager.is_initialized(): self._tmanager.gui_draw_tournament_bracket(screen, flags, trackplayer)
def _world_reset(self): self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self)._world_reset()
class GameWorld(object): """ represents the virtual space world and the objects in it Provides hit detection and radar between objects in the world """ def __init__(self, game, worldsize, pys=True): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler(begin=self.__beginCollideObject, post_solve=self.__collideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__planets = [] self.__nebulas = [] self.__objectListener = [] self.__toremove = [] self.__toadd = [] self.__active = True self.__pys = pys self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize)) if pys: logging.debug("Started gameloop for World %s", repr(self)) threading.Thread(None, self.__THREAD__gameloop, "WorldMap_gameloop_" + repr(self)).start() def mid_point(self, xoff = 0, yoff = 0): return intpos((self.width / 2 + xoff, self.height / 2 + yoff)) def endGameLoop(self): self.__active = False def newWorld(self, world): self.__pys = False time.sleep(0.2) # Remove All Objects for obj in self: self.pysremove(obj) # Copy objects from new world to original world for obj in world: #world.pysremove(obj) # need to remove object from space before adding to another self.append(obj, pys=True) self.__pys = True def __beginCollideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_pre_collision( arbiter.shapes ) if r != None: for i in r[1]: space.add_post_step_callback(i[0], i[1], i[2:]) return r[0] return True def __collideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_collision( arbiter.shapes, arbiter.total_impulse.length / 250.0 ) if r != None: for i in r: space.add_post_step_callback(i[0], i[1], i[2:]) #eif def causeExplosion(self, origin, radius, strength, force=False): logging.debug("Start Explosion %s, %d, %d [%d]", origin, radius, strength, thread.get_ident()) origin = pymunk.Vec2d(origin) for obj in self.__objects: if obj.explodable: for point in wrappos(obj.body.position, radius, self.size): if in_circle(origin, radius, point): logging.debug("Applying Explosion Force to #%d", obj.id) obj.body.apply_impulse((point - origin) * strength, (0,0)) break logging.debug("Done Explosion") def registerObjectListener(self, callback): self.__objectListener.append(callback) def __len__(self): return len(self.__objects) def __iter__(self): return self.__objects.__iter__() def __getitem__(self, key): return self.__objects.__getitem__(key) def __setitem__(self, key, value): return self.__objects.__setitem__(key, value) def has_key(self, key): return self.__objects.has_key(key) def append(self, item, pys=False): """ Call to add an object to the game world from threads outside the game loop """ logging.debug("Append Object to World %s [%d]", repr(item), thread.get_ident()) for objListener in self.__objectListener: objListener(item, True) logging.debug("SEMAPHORE ACQ append [%d]", thread.get_ident()) self.__addremovesem.acquire() if not self.__objects.has_key(item.id): self.__objects[item.id] = item if pys: item.addToSpace(self.__space) elif item in self.__toremove: self.__toremove.remove(item) else: self.__toadd.append(item) if isinstance(item, Planet) and not item in self.__planets and item.pull > 0: self.__planets.append(item) elif isinstance(item, Nebula) and not item in self.__nebulas and item.pull > 0: self.__nebulas.append(item) self.__addremovesem.release() logging.debug("SEMAPHORE REL append [%d]", thread.get_ident()) def remove(self, item): """ Call to remove an object from the game world """ del self[item] def pysremove(self, item): self.__delitem__(item, True) def __delitem__(self, key, pys=False): logging.debug("Removing Object from World %s [%d]", repr(key), thread.get_ident()) logging.debug("SEMAPHORE ACQ delitem [%d]", thread.get_ident()) self.__addremovesem.acquire() if self.__objects.has_key(key.id): if pys: key.removeFromSpace(self.__space) elif key in self.__toadd: self.__toadd.remove(key) else: self.__toremove.append(key) del self.__objects[key.id] if key in self.__planets: self.__planets.remove(key) elif key in self.__nebulas: self.__nebulas.remove(key) self.__addremovesem.release() logging.debug("SEMAPHORE REL delitem [%d]", thread.get_ident()) # Notify after removed, in case re-add same object for objListener in self.__objectListener: objListener(key, False) def __THREAD__gameloop(self): lasttime = MINIMUM_GAMESTEP_TIME try: while self.__active: if self.__pys: self.__space.step(MINIMUM_GAMESTEP_TIME) # Advance Physics Engine tstamp = time.time() # find objects in nebulas for neb in self.__nebulas: for shape in self.__space.shape_query(neb.shape): # Set value to two, so that if we're still in the nebula # for another loop, that we don't toggle in/out of nebula between slices # across threads if self.has_key(shape.id): self[shape.id].in_nebula = [2, neb] # update all game objects for obj in self: # self is dictionary # Wrap Object in World if obj.body.position[0] < 0: obj.body.position[0] += self.width elif obj.body.position[0] > self.width: obj.body.position[0] -= self.width if obj.body.position[1] < 0: obj.body.position[1] += self.height elif obj.body.position[1] > self.height: obj.body.position[1] -= self.height # Apply Gravity for Planets if obj.explodable: for planet in self.__planets: for point in wrappos(obj.body.position, planet.gravityFieldLength, self.size): if in_circle(planet.body.position, planet.gravityFieldLength, point): obj.body.apply_impulse((point - planet.body.position) * -planet.pull * lasttime, (0,0)) break # Update and Run Commands obj.update(lasttime) if obj.in_nebula != None: # slow down objects in Nebula if obj.body.velocity.length > 0.1: obj.body.velocity.length -= (obj.in_nebula[1].pull / obj.mass) * lasttime obj.in_nebula[0] -= 1 # decrement count if obj.in_nebula[0] <= 0: obj.in_nebula = None if obj.has_expired(): del self[obj] #next # game time notification self.__game.game_update(lasttime) # update time lasttime = time.time() - tstamp logging.debug("SEMAPHORE ACQ gameloop [%d]", thread.get_ident()) self.__addremovesem.acquire() # NOTE ISSUE #68 - If server is slow, can be adding and removing object in same step... add first, so we can immediately remove instead of crash # TODO: just look for common set between two lists and remove... or have each list check the other first before adding to the lists... # probably best to refactor to PyMunk 4.0.0 and be able to have PyMunk handle adding/removing objects during physics step. # add objects for item in self.__toadd: logging.info("World Adding #%d to Physics Engine %s", item.id, repr(item)) item.addToSpace(self.__space) self.__toadd = [] #remove objects for item in self.__toremove: logging.info("World Removing #%d from Physics Engine %s", item.id, repr(item)) item.removeFromSpace(self.__space) self.__toremove = [] self.__addremovesem.release() logging.debug("SEMAPHORE REL gameloop [%d]", thread.get_ident()) # minimum to conserve resources if lasttime < MINIMUM_GAMESTEP_TIME: time.sleep(MINIMUM_GAMESTEP_TIME - lasttime) lasttime = MINIMUM_GAMESTEP_TIME #else: #logg#ging.debug("Can't keep at 50fps at %dfps", 1.0 / lasttime) #eif #wend except: print "EXCEPTION IN GAMELOOP" logging.exception("FATAL Error in game loop!!!") logging.error(traceback.format_exc()) print traceback.format_exc() self.gameerror = True logging.debug("Gameloop Ended") def getObjectsInArea(self, pos, radius, force=False): logging.debug("Get Objects In Area %s %d (%s) [%d]", repr(pos), radius, repr(force), thread.get_ident()) objList = [] for obj in self.__objects.values(): for point in wrappos(obj.body.position, radius, self.size): if in_circle(pos, radius, point): objList.append(obj) break #eif #next #next return objList def getObjectData(self, obj): objData = {} # Convert Type of Object to String objData["TYPE"] = friendly_type(obj) objData["ID"] = obj.id objData["POSITION"] = intpos(obj.body.position) objData["SPEED"] = obj.body.velocity.length # TODO: deal with -0.0 case OR match physics library coordinates? objData["DIRECTION"] = -obj.body.velocity.angle_degrees # 30 = -120?, -80 = -10 #objData["VELOCITYDIRECTION"] = obj.velocity.direction objData["MAXSPEED"] = obj.body.velocity_limit objData["CURHEALTH"] = obj.health.value objData["MAXHEALTH"] = obj.health.maximum objData["CURENERGY"] = obj.energy.value objData["MAXENERGY"] = obj.energy.maximum objData["ENERGYRECHARGERATE"] = obj.energyRechargeRate objData["MASS"] = obj.mass objData["HITRADIUS"] = obj.radius #TODO: Move to entites that have this i.e. physicalround? objData["TIMEALIVE"] = obj.timealive obj.getExtraInfo(objData) self.__game.game_get_extra_radar_info(obj, objData) return objData def getEnvironment(self, ship, radarlevel=0, target=-1, getMessages=False): #TODO: abstract radar to game level? radardata = None if radarlevel > 0: objs = self.getObjectsInArea(ship.body.position, ship.radarRange) #TODO: Need to wait lock the removing of ships with accessing...??? if ship in objs: objs.remove(ship) # Get rid of self... # remove ships with cloak for x in xrange(len(objs) - 1, -1, -1): if isinstance(objs[x], Ship) and objs[x].commandQueue.containstype(CloakCommand): del objs[x] #eif #next if radarlevel == 1: # number of objects radardata = [] for i in xrange(len(objs)): radardata.append({}) elif radarlevel == 2: # (pos, id) list radardata = [] for e in objs: radardata.append({"POSITION":intpos(e.body.position), "ID":e.id}) #next elif radarlevel == 3: for obj in objs: if obj.id == target: radardata = [self.getObjectData(obj)] break elif radarlevel == 4: # (pos, id, type) radardata = [] for e in objs: radardata.append({"POSITION":intpos(e.body.position), "ID":e.id, "TYPE":friendly_type(e)}) #next elif radarlevel == 5: radardata = [] for e in objs: radardata.append(self.getObjectData(e)) #next #eif #eif msgQueue = [] if getMessages: msqQueue = list(ship.messageQueue) return {"SHIPDATA": self.getObjectData(ship), "RADARLEVEL": radarlevel, "RADARDATA": radardata, "GAMEDATA": self.__game.game_get_extra_environment(ship.player), "MESSAGES": msgQueue, }
class BaubleHuntGame(BaseBaubleGame): def __init__(self, cfgobj): self._respawn = cfgobj.getboolean("BaubleHunt", "respawn_bauble_on_collect") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self._mv = BaseBaubleGame.VALUE_TABLE[-1][1] self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size") def game_get_info(self): return {"GAMENAME": "BaubleHunt"} def player_added(self, player, reason): super(BaubleHuntGame, self).player_added(player, reason) player.carrying = [] if reason == BasicGame._ADD_REASON_REGISTER_: player.totalcollected = 0 elif reason == BasicGame._ADD_REASON_START_: player.totalcollected = 0 self.__addHomeBase(player) elif self.__bases.has_key(player.netid): self.__bases[player.netid].newOwner(player.object) def __addHomeBase(self, player, force=False): logging.info("Add HomeBase (%s) for Player %d", repr(force), player.netid) # add player bauble b = Outpost(self.getHomeBasePosition(), player.object) self.__bases[player.netid] = b self.world.append(b) logging.info("Done Adding HomeBase") # Ignores Baubles def getHomeBasePosition(self): pos = (0, 0) x = 0 n = 1 while n > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) objs = self.world.getObjectsInArea(pos, 200) for i in xrange(len(objs) - 1, -1, -1): if isinstance(objs[i], Bauble): del objs[i] n = len(objs) return pos def world_add_remove_object(self, wobj, added): # Check if this is a high-value bauble to add to our list of ones to pass to the client if isinstance(wobj, Bauble): if wobj.value == self._mv: self.__baubles[wobj.id] = wobj return super(BaubleHuntGame, self).world_add_remove_object(wobj, added) def world_physics_pre_collision(self, obj1, obj2): ship, other = aligninstances(obj1, obj2, Ship, Entity) if ship != None: if isinstance(other, Outpost): logging.info("Ship #%d hit their base", ship.id) return [False, [self.depositBaubles, ship, other]] elif isinstance(other, Bauble): return [False, [self.collectBaubles, ship, other]] return super(BaubleHuntGame, self).world_physics_pre_collision(obj1, obj2) def get_player_cargo_value(self, player): return sum(b.value for b in player.carrying) def get_player_cargo_weight(self, player): return sum(b.weight for b in player.carrying) def collectBaubles(self, ship, bauble): logging.info("Collected Baubles Ship #%d", ship.id) if self.get_player_cargo_weight( ship.player) + bauble.weight <= self.__maxcarry: ship.player.carrying.append(bauble) ship.player.sound = "BAUBLE" if self.__baubles.has_key(bauble.id): del self.__baubles[bauble.id] bauble.destroyed = True if self._respawn: Bauble.spawn(self.world, self.cfg) else: logging.info("Player #%d Cargo Full", ship.id) #eif logging.info("Done Collecting Baubles #%d", ship.id) def depositBaubles(self, ship, home): logging.info("Player Depositing Baubles #%d", ship.id) for b in ship.player.carrying: ship.player.update_score(b.value) home.stored += b.value ship.player.totalcollected += len(ship.player.carrying) if len(ship.player.carrying) > 0: ship.player.sound = "COLLECT" ship.player.carrying = [] def player_died(self, player, gone): # if ship destroyed, put baubles stored back for b in player.carrying: b.body.position = (player.object.body.position[0] + random.randint(-36, 36), player.object.body.position[1] + random.randint(-36, 36)) b.destroyed = False # reset so that it won't get cleaned up if b.value == self._mv: self.__baubles[b.id] = b self.world.append(b) #self.world.causeExplosion(player.object.body.position, 32, 1000) # Remove player's base if they're gone if gone and self.__bases.has_key(player.netid): self.world.remove(self.__bases[player.netid]) del self.__bases[player.netid] return super(BaubleHuntGame, self).player_died(player, gone) def game_get_extra_environment(self, player): if player.netid in self.__bases: # Check if Player still around? v = 0 w = 0 for b in player.carrying: v += b.value w += b.weight baubles = [] for b in self.__baubles: baubles.append(intpos(b.body.position)) env = super(BaubleHuntGame, self).game_get_extra_environment(player) env.update({ "POSITION": intpos(self.__bases[player.netid].body.position), "BAUBLES": baubles, "STORED": len(player.carrying), "STOREDVALUE": v, "COLLECTED": player.totalcollected, "WEIGHT": w }) return env else: return {} def game_get_extra_radar_info(self, obj, objdata, player): """ Called by the World when the obj is being radared """ super(BaubleHuntGame, self).game_get_extra_radar_info(obj, objdata, player) if hasattr(obj, "player"): objdata["NUMSTORED"] = len(obj.player.carrying) objdata["VALUE"] = self.get_player_cargo_value(obj.player) def player_get_stat_string(self, player): return str(int(player.score)) + " in " + str( player.totalcollected) + " : " + player.name + " c.v. " + str( sum(b.value for b in player.carrying)) + " in " + str( len(player.carrying)) def gui_draw_game_world_info(self, surface, flags, trackplayer): for player in self.game_get_current_player_list(): obj = player.object if obj != None: # draw number of objects carried text = debugfont().render(repr(len(player.carrying)), False, player.color) surface.blit( text, (obj.body.position[0] + 30, obj.body.position[1] - 4)) # draw line between player and HomeBase if flags["DEBUG"] and self.__bases.has_key(player.netid): pygame.draw.line( surface, player.color, intpos(obj.body.position), intpos(self.__bases[player.netid].body.position)) # draw number of baubles carried by player def round_start(self): logging.info("Game Start") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).round_start()
def round_start(self): logging.info("Game Start") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).round_start()
class GameWorld(object): """ represents the virtual space world and the objects in it Provides hit detection and radar between objects in the world """ def __init__(self, game, worldsize, objlistener=None): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not objlistener: initial listner callback function """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler(begin=self.__beginCollideObject, post_solve=self.__collideObject, separate=self.__endCollideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__influential = [] if objlistener == None: self.__objectListener = [] else: self.__objectListener = [objlistener] self.__toremove = [] self.__toadd = [] self.__active = False self.__started = False self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize)) def start(self): if not self.__started: self.__started = True self.__active = True self.__pys = True logging.debug("Started gameloop for World %s", repr(self)) threading.Thread(None, self.__THREAD__gameloop, "WorldMap_gameloop_" + repr(self)).start() def mid_point(self, xoff = 0, yoff = 0): return intpos((self.width / 2 + xoff, self.height / 2 + yoff)) def endGameLoop(self): self.__active = False def destroy_all(self): self.__pys = False # disable physics step while we add/remove all objects time.sleep(0.2) # Remove All Objects for obj in self: self.pysremove(obj) self.__pys = True def __beginCollideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_pre_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object ) if r != None: if r == False or r == True: return r for i in r[1:]: space.add_post_step_callback(i[0], i[1], i[2]) return r[0] return True def __collideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object, arbiter.total_impulse.length / 250.0 ) if r != None: for i in r: space.add_post_step_callback(i[0], i[1], i[2]) #eif def __endCollideObject(self, space, arbiter): # when object is destroyed in callback, arbiter may be empty if hasattr(arbiter, "shapes"): self.__game.world_physics_end_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object ) def causeExplosion(self, origin, radius, strength, force=False): logging.debug("Start Explosion %s, %d, %d [%d]", origin, radius, strength, thread.get_ident()) origin = pymunk.Vec2d(origin) for obj in self.__objects: if obj.explodable: for point in wrappos(obj.body.position, radius, self.size): if in_circle(origin, radius, point): logging.debug("Applying Explosion Force of strength %d impulse %s to #%d", strength, repr((point - origin) * strength), obj.id) obj.body.apply_impulse((point - origin) * strength, (0,0)) break logging.debug("Done Explosion") def registerObjectListener(self, callback): if callback not in self.__objectListener: self.__objectListener.append(callback) def __len__(self): return len(self.__objects) def __iter__(self): return self.__objects.__iter__() def __getitem__(self, key): return self.__objects.__getitem__(key) def __setitem__(self, key, value): return self.__objects.__setitem__(key, value) def has_key(self, key): return self.__objects.has_key(key) def append(self, item, pys=False): """ Call to add an object to the game world from threads outside the game loop """ logging.debug("Append Object to World %s [%d]", repr(item), thread.get_ident()) for objListener in self.__objectListener: objListener(item, True) logging.debug("SEMAPHORE ACQ append [%d]", thread.get_ident()) self.__addremovesem.acquire() if not self.__objects.has_key(item.id): self.__objects[item.id] = item if pys: item.addToSpace(self.__space) elif item in self.__toremove: self.__toremove.remove(item) else: self.__toadd.append(item) if isinstance(item, Influential) and item.influence_range > 0: self.__influential.append(item) self.__addremovesem.release() logging.debug("SEMAPHORE REL append [%d]", thread.get_ident()) def remove(self, item): """ Call to remove an object from the game world """ del self[item] def pysremove(self, item): self.__delitem__(item, True) def __delitem__(self, key, pys=False): logging.debug("Removing Object from World %s [%d]", repr(key), thread.get_ident()) # Notify each item this may be in that it's no longer colliding # HACK: Get around issue with PyMunk not telling us shapes when object removed already before separate callback if len(key.in_celestialbody) > 0: for item in key.in_celestialbody[:]: item.collide_end(key) logging.debug("SEMAPHORE ACQ delitem [%d]", thread.get_ident()) self.__addremovesem.acquire() if self.__objects.has_key(key.id): if pys: key.removeFromSpace(self.__space) elif key in self.__toadd: self.__toadd.remove(key) else: self.__toremove.append(key) del self.__objects[key.id] if key in self.__influential: self.__influential.remove(key) self.__addremovesem.release() logging.debug("SEMAPHORE REL delitem [%d]", thread.get_ident()) # Notify after removed, in case re-add same object for objListener in self.__objectListener: objListener(key, False) def __THREAD__gameloop(self): lasttime = MINIMUM_GAMESTEP_TIME try: while self.__active: if self.__pys: self.__space.step(MINIMUM_GAMESTEP_TIME) # Advance Physics Engine tstamp = time.time() # update all game objects for obj in self: # self is dictionary # Wrap Object in World if obj.body.position[0] < 0: obj.body.position[0] += self.width elif obj.body.position[0] > self.width: obj.body.position[0] -= self.width if obj.body.position[1] < 0: obj.body.position[1] += self.height elif obj.body.position[1] > self.height: obj.body.position[1] -= self.height # Apply Gravity for Planets if obj.gravitable: for influencer in self.__influential: for point in wrappos(obj.body.position, influencer.influence_range, self.size): if in_circle(influencer.body.position, influencer.influence_range, point): influencer.apply_influence(obj, point, lasttime) break # Update and Run Commands obj.update(lasttime) if obj.has_expired() or obj.destroyed: del self[obj] #next # game time notification self.__game.game_update(lasttime) # update time lasttime = time.time() - tstamp logging.debug("SEMAPHORE ACQ gameloop [%d]", thread.get_ident()) self.__addremovesem.acquire() # NOTE ISSUE #68 - If server is slow, can be adding and removing object in same step... add first, so we can immediately remove instead of crash # TODO: just look for common set between two lists and remove... or have each list check the other first before adding to the lists... # probably best to refactor to PyMunk 4.0.0 and be able to have PyMunk handle adding/removing objects during physics step. # add objects for item in self.__toadd: logging.info("World Adding #%d to Physics Engine %s", item.id, repr(item)) item.addToSpace(self.__space) self.__toadd = [] #remove objects for item in self.__toremove: logging.info("World Removing #%d from Physics Engine %s", item.id, repr(item)) item.removeFromSpace(self.__space) self.__toremove = [] self.__addremovesem.release() logging.debug("SEMAPHORE REL gameloop [%d]", thread.get_ident()) # minimum to conserve resources if lasttime < MINIMUM_GAMESTEP_TIME: time.sleep(MINIMUM_GAMESTEP_TIME - lasttime) lasttime = MINIMUM_GAMESTEP_TIME #else: #logg#ging.debug("Can't keep at 50fps at %dfps", 1.0 / lasttime) #eif #wend except: print "EXCEPTION IN GAMELOOP" logging.exception("FATAL Error in game loop!!!") logging.info(traceback.format_exc()) logging.error(traceback.format_exc()) print traceback.format_exc() self.gameerror = True logging.debug("Gameloop Ended") def getObjectsInArea(self, pos, radius, radar=False): """ Returns objects within the given radius from the position (even wrapping around edge of world), pass radar = True if for environment """ logging.debug("Get Objects In Area %s %d [%d]", repr(pos), radius, thread.get_ident()) objList = [] for obj in self.__objects.values(): for point in wrappos(obj.body.position, radius, self.size): if in_circle(pos, radius, point): objList.append(obj) break #eif #next #next return objList def get_count_of_objects(self, type): tname = type.__name__ count = 0 for obj in self.__objects.values(): if obj.__class__.__name__ == tname: count += 1 #eif #next return count def getObjectData(self, obj, player): #TODO: Move these properties to the 'getExtraInfo' of the base Entity and have child classes call super... objData = {} # Convert Type of Object to String objData["TYPE"] = friendly_type(obj) objData["ID"] = obj.id objData["POSITION"] = intpos(obj.body.position) objData["SPEED"] = obj.body.velocity.length # TODO: deal with -0.0 case OR match physics library coordinates? objData["DIRECTION"] = -obj.body.velocity.angle_degrees % 360 # 30 = -120?, -80 = -10 #objData["VELOCITYDIRECTION"] = obj.velocity.direction objData["MAXSPEED"] = obj.body.velocity_limit objData["CURHEALTH"] = obj.health.value objData["MAXHEALTH"] = obj.health.maximum objData["CURENERGY"] = obj.energy.value objData["MAXENERGY"] = obj.energy.maximum objData["ENERGYRECHARGERATE"] = obj.energyRechargeRate objData["MASS"] = obj.mass objData["HITRADIUS"] = obj.radius #TODO: Move to entites that have this i.e. physicalround? objData["TIMEALIVE"] = obj.timealive objData["INBODY"] = (len(obj.in_celestialbody) > 0) obj.getExtraInfo(objData, player) self.__game.game_get_extra_radar_info(obj, objData, player) return objData def getEnvironment(self, ship, radarlevel=0, target=-1, getMessages=False): #TODO: abstract radar to game level? radardata = None if radarlevel > 0: objs = self.getObjectsInArea(ship.body.position, ship.radarRange, True) #TODO: Need to wait lock the removing of ships with accessing...??? if ship in objs: objs.remove(ship) # Get rid of self... # remove ships with cloak or in Nebula # TODO: Should there be an 'invisible' flag? Would need more testing. Would a degradation/range be useful? for x in xrange(len(objs) - 1, -1, -1): if isinstance(objs[x], Ship) and (objs[x].commandQueue.containstype(CloakCommand) \ or any(isinstance(y, Nebula) for y in objs[x].in_celestialbody)): del objs[x] #eif #next if radarlevel == 1: # number of objects radardata = [] for i in xrange(len(objs)): radardata.append({}) elif radarlevel == 2: # (pos, id) list radardata = [] for e in objs: radardata.append({"POSITION":intpos(e.body.position), "ID":e.id}) #next elif radarlevel == 3: for obj in objs: if obj.id == target: radardata = [self.getObjectData(obj, ship.player)] break elif radarlevel == 4: # (pos, id, type) radardata = [] for e in objs: radardata.append({"POSITION":intpos(e.body.position), "ID":e.id, "TYPE":friendly_type(e)}) #next elif radarlevel == 5: radardata = [] for e in objs: radardata.append(self.getObjectData(e, ship.player)) #next #eif #eif msgQueue = [] if getMessages: msqQueue = list(ship.messageQueue) return {"SHIPDATA": self.getObjectData(ship, ship.player), "RADARLEVEL": radarlevel, "RADARDATA": radardata, "GAMEDATA": self.__game.game_get_extra_environment(ship.player), "MESSAGES": msgQueue, }
class BaubleHuntGame(BasicGame): VALUE_TABLE = [] def __init__(self, cfgobj): bb = cfgobj.getfloat("BaubleHunt", "bauble_percent_blue") yb = cfgobj.getfloat("BaubleHunt", "bauble_percent_yellow") rb = cfgobj.getfloat("BaubleHunt", "bauble_percent_red") self._mv = cfgobj.getint("BaubleHunt", "bauble_points_red") BaubleHuntGame.VALUE_TABLE = [(bb, cfgobj.getint("BaubleHunt", "bauble_points_blue")), (bb+yb, cfgobj.getint("BaubleHunt", "bauble_points_yellow")), (bb+yb+rb, self._mv)] self._respawn = cfgobj.getboolean("BaubleHunt", "respawn_bauble_on_collect") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size") def game_get_info(self): return {"GAMENAME": "BaubleHunt"} def player_added(self, player, reason): super(BaubleHuntGame, self).player_added(player, reason) player.carrying = [] if reason == BasicGame._ADD_REASON_REGISTER_: player.totalcollected = 0 elif reason == BasicGame._ADD_REASON_START_: player.totalcollected = 0 self.__addHomeBase(player) elif self.__bases.has_key(player.netid): self.__bases[player.netid].newOwner(player.object) def __addHomeBase(self, player, force=False): logging.info("Add HomeBase (%s) for Player %d", repr(force), player.netid) # add player bauble b = Outpost(self.getHomeBasePosition(), player.object) self.__bases[player.netid] = b self.world.append(b) logging.info("Done Adding HomeBase") # Ignores Baubles def getHomeBasePosition(self): pos = (0,0) x = 0 n = 1 while n > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) objs = self.world.getObjectsInArea(pos, 200) for i in xrange(len(objs)-1, -1, -1): if isinstance(objs[i], Bauble): del objs[i] n = len(objs) return pos def world_add_remove_object(self, wobj, added): # Check if this is a high-value bauble to add to our list of ones to pass to the client if isinstance(wobj, Bauble): if wobj.value == self._mv: self.__baubles[wobj.id] = wobj return super(BaubleHuntGame, self).world_add_remove_object(wobj, added) def world_physics_pre_collision(self, obj1, obj2): ship, other = aligninstances(obj1, obj2, Ship, Entity) if ship != None: if isinstance(other, Outpost): logging.info("Ship #%d hit their base", ship.id) return [ False, [self.depositBaubles, ship, other] ] elif isinstance(other, Bauble): return [ False, [self.collectBaubles, ship, other] ] return super(BaubleHuntGame, self).world_physics_pre_collision(obj1, obj2) def collectBaubles(self, ship, bauble): logging.info("Collected Baubles Ship #%d", ship.id) if len(ship.player.carrying) < self.__maxcarry: ship.player.carrying.append(bauble) ship.player.sound = "BAUBLE" if self.__baubles.has_key(bauble.id): del self.__baubles[bauble.id] bauble.destroyed = True if self._respawn: Bauble.spawn(self.world, self.cfg) #eif logging.info("Done Collecting Baubles #%d", ship.id) def depositBaubles(self, ship, home): logging.info("Player Depositing Baubles #%d", ship.id) for b in ship.player.carrying: self.player_update_score(ship.player, b.value) home.stored += b.value ship.player.totalcollected += len(ship.player.carrying) if len(ship.player.carrying) > 0: ship.player.sound = "COLLECT" ship.player.carrying = [] def player_died(self, player, gone): # if ship destroyed, put baubles stored back for b in player.carrying: b.body.position = (player.object.body.position[0] + random.randint(-10, 10), player.object.body.position[1] + random.randint(-10, 10)) b.destroyed = False # reset so that it won't get cleaned up if b.value == self._mv: self.__baubles[b.id] = b self.world.append(b) self.world.causeExplosion(player.object.body.position, 32, 1000) # Remove player's base if they're gone if gone and self.__bases.has_key(player.netid): self.world.remove(self.__bases[player.netid]) del self.__bases[player.netid] return super(BaubleHuntGame, self).player_died(player, gone) def game_get_extra_environment(self, player): if player.netid in self.__bases: # Check if Player still around? v = 0 for b in player.carrying: v += b.value baubles = [] for b in self.__baubles: baubles.append(intpos(b.body.position)) env = super(BaubleHuntGame, self).game_get_extra_environment(player) env.update({"POSITION": intpos(self.__bases[player.netid].body.position), "BAUBLES": baubles, "STORED": len(player.carrying), "STOREDVALUE": v, "COLLECTED": player.totalcollected}) return env else: return {} def game_get_extra_radar_info(self, obj, objdata, player): """ Called by the World when the obj is being radared """ super(BaubleHuntGame, self).game_get_extra_radar_info(obj, objdata, player) if hasattr(obj, "player"): objdata["NUMSTORED"] = len(obj.player.carrying) def player_get_stat_string(self, player): return str(int(player.score)) + " in " + str(player.totalcollected) + " : " + player.name + " c.v. " + str(sum(b.value for b in player.carrying)) + " in " + str(len(player.carrying)) def gui_draw_game_world_info(self, surface, flags, trackplayer): for player in self.game_get_current_player_list(): obj = player.object if obj != None: # draw number of objects carried text = debugfont().render(repr(len(player.carrying)), False, player.color) surface.blit(text, (obj.body.position[0]+30, obj.body.position[1]-4)) # draw line between player and HomeBase if flags["DEBUG"] and self.__bases.has_key(player.netid): pygame.draw.line(surface, player.color, intpos(obj.body.position), intpos(self.__bases[player.netid].body.position)) # draw number of baubles carried by player def round_start(self): logging.info("Game Start") self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).round_start()
class DiscoveryQuestGame(BasicGame): """ Discovery Quest is a game of exploration. Ships must 'Scan' various objects in order to accumulate points. Every type of object in the game is worth a different number of points. Scanning is different from Radar, and requires a precise ID and takes more time and energy to perform. It also has a limited range which you must stay within for the whole duration of the scan. You CANNOT scan the same object more than once for points. Ships must establish an 'Outpost' by scanning it. Once they have done so, they will start to receive 'Missions'. They initially won't score points for scanned objects until an Outpost is established. All points for things scanned before making an Outpost will be awarded when an Outpost is established. A Mission dictates which objects a ship should go scan. If a ship goes and scans ONLY those things, bonus points will be awarded when they return to their outpost and Scan it again. Scanning their outpost will always give them a new mission. Your Ship being destroyed clears/fails your mission, so you must return to your established outpost if you want a new one. """ def __init__(self, cfgobj): self._outposts = ThreadSafeDict() super(DiscoveryQuestGame, self).__init__(cfgobj) self.mustbase = cfgobj.getboolean("DiscoveryQuest", "establish_homebase") self.usemissions = cfgobj.getboolean("DiscoveryQuest", "missions") self._missions = cfgobj.get("DiscoveryQuest", "mission_objectives").split(",") self.scantime = cfgobj.getfloat("DiscoveryQuest", "scan_time") self.scanrange = cfgobj.getint("DiscoveryQuest", "scan_range") self.scanduration = cfgobj.getint("DiscoveryQuest", "scan_duration") self.outpostdist = cfgobj.getint("DiscoveryQuest", "ship_spawn_dist") self.limitwarp = cfgobj.getboolean("DiscoveryQuest", "disable_warp_in_nebula") def game_get_info(self): return {"GAMENAME": "DiscoveryQuest"} def player_reset_mission(self, player): if self.usemissions: # get a number of missions for each thing player.mission = [] player.scanned = [] player.failed = False for i in xrange( cfg_rand_min_max(self.cfg, "DiscoveryQuest", "mission_num")): player.mission.append( random.choice(self._missions) ) # TODO: validate that this player can still scan those types of objects def player_added(self, player, reason): if reason == BasicGame._ADD_REASON_REGISTER_: player.buffervalue = 0 # stored points before scanning first output, if outpost is required. player.outpost = None # home base obj player.lastids = [] # last ids scanned player.scantimes = {} # timers for scans, id: timestamp player.mission = [] # List of strings of items to scan player.scanned = [] # ids scanned by player player.failed = True # whether current mission has failed, starts as true as no mission to start return super(DiscoveryQuestGame, self).player_added(player, reason) def player_get_start_position(self, force=False): # pick a random outpost to spawn the player besides out = intpos(random.choice(self._outposts.values()).body.position) pos = (random.randint(out[0] - self.outpostdist, out[0] + self.outpostdist), random.randint(out[1] - self.outpostdist, out[1] + self.outpostdist)) x = 0 while len(self.world.getObjectsInArea( pos, self.outpostdist / 2)) > 0 and x < 15: x += 1 pos = (random.randint(out[0] - self.outpostdist, out[0] + self.outpostdist), random.randint(out[1] - self.outpostdist, out[1] + self.outpostdist)) return pos def player_died(self, player, gone): if gone and player.outpost != None: player.outpost.home_for.remove(player) player.outpost = None if gone: for obj in self.world: if player in obj.scanned_by: obj.scanned_by.remove(player) return super(DiscoveryQuestGame, self).player_died(player, gone) def player_get_stat_string(self, player): """ Should return a string with information about the player and their current score. Defaults to: primary_score secondary_score : player_name """ if player.buffervalue > 0: return "%d %s B: %d" % (getattr( player, self._primary_victory), player.name, getattr(player, self._secondary_victory)) else: return "%d %s" % (getattr(player, self._primary_victory), player.name) def world_add_remove_object(self, wobj, added): if isinstance(wobj, Outpost): if not added: del self._outposts[wobj.id] else: self._outposts[wobj.id] = wobj if added: wobj.scanned_by = [] # objects keep track of who scans them # give object point value opt = "points_" + friendly_type(wobj).lower() if self.cfg.has_option("DiscoveryQuest", opt): wobj.value = self.cfg.getint("DiscoveryQuest", opt) else: #guard wobj.value = 0 else: # clean-up reference to scantime on obj death for player in self.game_get_current_player_list(): if player in wobj.scanned_by: del player.scantimes[wobj.id] return super(DiscoveryQuestGame, self).world_add_remove_object(wobj, added) def game_get_extra_environment(self, player): env = super(DiscoveryQuestGame, self).game_get_extra_environment(player) if player.outpost != None: env["POSITION"] = intpos(player.outpost.body.position) env["FAILED"] = player.failed env["MISSION"] = player.mission obj = player.object if obj != None: cur = [] for cmd in obj.commandQueue: if isinstance(cmd, ScanCommand): cur.append(cmd.target) env["CURIDS"] = cur env["SUCIDS"] = player.lastids player.lastids = [] return env def game_get_extra_radar_info(self, obj, objdata, player): super(DiscoveryQuestGame, self).game_get_extra_radar_info(obj, objdata, player) # return object's value for scanning if hasattr(obj, "value"): objdata["VALUE"] = obj.value objdata["SUCCESS"] = player in obj.scanned_by def server_process_network_message(self, ship, cmdname, cmddict={}): """ Called by the server when it doesn't know how to convert a network message into a Ship Command. This function is used to create commands custom to the game itself. Parameters: ship: Ship object which will process the Command cmdname: string network shortcode for command, set overriding getName in ShipCommand on Client cmddict: dictionary of properties of a command, set using private fields on ShipCommand class Return: return a server 'Command' object, a string indicating an error, or None """ if cmdname == GAME_CMD_SCAN: if cmddict.has_key("TARGET") and isinstance( cmddict["TARGET"], int) and cmddict["TARGET"] > 0: return ScanCommand(ship, self, cmddict["TARGET"]) else: return "Discovery Quest Scan Command requires a target id." def server_process_command(self, ship, command): # Discovery Quest prevents warp in a nebula if self.limitwarp and isinstance(command, WarpCommand): for body in ship.in_celestialbody: if isinstance(body, Nebula): logging.info( "Preventing Ship #%d From Warping As In Nebula", ship.id) return "Discovery Quest Rule - Can't warp when in Nebula" return command def dq_finished_scan(self, ship, obj, success): if obj != None: logging.info( "Ship #%d finished scan of object #%d and was successful? [%s]", ship.id, obj.id, repr(success)) else: logging.info( "Ship #%d finished scan of unknown object (never saw id)", ship.id) if success: ship.player.lastids.append(obj.id) if isinstance(obj, Outpost) and (ship.player in obj.home_for or ship.player.outpost == None): # scanned player's own Outpost, do Mission Stuff HERE if ship.player.outpost == None: # establish as ship's Outpost ship.player.outpost = obj obj.home_for.append(ship.player) ship.player.update_score( ship.player.buffervalue + obj.value * 2 ) # Score initial points + 2*outpost value for establishing base ship.player.buffervalue = 0 ship.player.sound = "SUCCESS" if self.usemissions and not ship.player.failed and len( ship.player.mission) == 0: # completed mission exactly points = 0 # tally points of scanned objects for obj in ship.player.scanned: points += obj.value ship.player.sound = "MISSION" ship.player.update_score(points * self.cfg.getfloat( "DiscoveryQuest", "mission_bonus_multiplier")) self.player_reset_mission(ship.player) elif ship.player in obj.scanned_by: logging.info("Ship #%d has ALREADY scanned object #%d", ship.id, obj.id) else: # mission checks if friendly_type(obj) in ship.player.mission: ship.player.mission.remove(friendly_type(obj)) else: ship.player.failed = True ship.player.scanned.append(obj) # track obj scan ship.player.scantimes[obj.id] = 0 obj.scanned_by.append(ship.player) # update scores ship.player.sound = "SUCCESS" if ship.player.outpost != None or not self.mustbase: # or we don't require bases for points ship.player.update_score(obj.value) else: #haven't found outpost, need to buffer points ship.player.buffervalue += obj.value #end dq_finished_scan def game_update(self, t): super(DiscoveryQuestGame, self).game_update(t) # check timeout of scan duration (if enabled) if self.scanduration > 0: for player in self.game_get_current_player_list(): for id in player.scantimes.keys(): player.scantimes[id] += t if player.scantimes[id] >= self.scanduration: obj = self.world[id] obj.scanned_by.remove(player) del player.scantimes[id] #end game_update def gui_draw_game_world_info(self, surface, flags, trackplayer): for player in self.game_get_current_player_list(): obj = player.object if obj != None: bp = intpos(obj.body.position) wrapcircle(surface, (0, 255, 255), bp, self.scanrange, self.world.size, 1) # Scan Range if self.usemissions: text = debugfont().render( "%s [%s]" % (repr(player.mission), player.failed), False, (0, 255, 255)) surface.blit(text, (bp[0] - text.get_width() / 2, bp[1] - 6)) if trackplayer != None: curs = [] curf = [] obj = trackplayer.object if obj != None: # Draw Success/Failure Circles around Current Scan Targets for cmd in obj.commandQueue: if isinstance(cmd, ScanCommand): if cmd.success: obj = self.world[cmd.target] wrapcircle(surface, (255, 255, 0), intpos(obj.body.position), obj.radius + 6, self.world.size, 2) else: obj = self.world[cmd.target] wrapcircle(surface, (255, 0, 0), intpos(obj.body.position), obj.radius + 6, self.world.size, 2) # Draw Circles around scanned entities for id, scantime in trackplayer.scantimes.iteritems(): obj = self.world[id] #if trackplayer in obj.scanned_by: if self.scanduration > 0: c = 160 * (scantime / self.scanduration) else: c = 0 wrapcircle(surface, (0, 255 - c, 255 - c), intpos(obj.body.position), obj.radius + 4, self.world.size, 4)
class BasicGame(object): """ The BasicGame defines all notions of the basic mechanics of Space Battle as well as an infrastructure to build upon. The BasicGame provides information about a player's 'score', 'bestscore', 'deaths' by default. It also keeps track of the 'highscore' in a variety of manners and can run a tournament to advance players to a final round. Public Attributes: world: world object server: server object laststats: previous rounds scores Protected Attributes: _players: dictionary of players keyed by their network id (player.netid) _tournament: boolean True if playing tournament _tournamentinitialized: boolean True if tournament has been setup _highscore: current highscore of the game based on primary_victory_attr _leader: current leader of the game """ _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 def __init__(self, cfgobj): self.cfg = cfgobj self.__started = False # Load Config self.__autostart = cfgobj.getboolean("Game", "auto_start") self.__allowafterstart = cfgobj.getboolean("Game", "allow_after_start") self.__allowreentry = cfgobj.getboolean("Server", "allow_re-entry") self.__resetworldround = self.cfg.getboolean("Tournament", "reset_world_each_round") self.__roundtime = cfgobj.getint("Tournament", "round_time") self._disconnect_on_death = cfgobj.getboolean("Game", "disconnect_on_death") self._reset_score_on_death = cfgobj.getboolean("Game", "reset_score_on_death") self._points_lost_on_death = cfgobj.getint("Game", "points_lost_on_death") self._points_initial = cfgobj.getint("Game", "points_initial") self._primary_victory = cfgobj.get("Game", "primary_victory_attr") self._primary_victory_high = cfgobj.getboolean("Game", "primary_victory_highest") self._secondary_victory = cfgobj.get("Game", "secondary_victory_attr") self._secondary_victory_high = cfgobj.getboolean("Game", "secondary_victory_highest") # TODO: Create Tournament Class self._tournament = cfgobj.getboolean("Tournament", "tournament") self._tournamentinitialized = False if self._tournament: self.__autostart = False else: self.__roundtime = 0 # don't want to return a big round time set in a config, if it's not used. self._tournamentnumgroups = cfgobj.getint("Tournament", "tournament_groups") self._tournamentgroups = [] self._tournamentcurrentgroup = 0 self._tournamentfinalgroup = [] self._tournamentfinalround = False self._tournamentfinalwinner = None self.laststats = None self._highscore = 0 # highest score achieved during game self._leader = None # player object of who's in lead self.server = None # set when server created self.__teams = ThreadSafeDict() self._players = ThreadSafeDict() self.__aiships = ThreadSafeDict() self.__timer = None self.__leaderboard_cache = self.game_get_current_player_list() # Load World self.world = self.world_create() self.world.registerObjectListener(self.world_add_remove_object) if self.__autostart: self.round_start() def end(self): """ Called when exiting GUI to terminate game. """ logging.info("Ending Game") self._tournament = True # force it to not restart timers again self.round_over() logging.info("Ending World") self.world.endGameLoop() #region Round/Timing Functions def _round_start_timer(self): """ Called automatically by round_start. """ if self.__roundtime > 0 and self._tournament: self.__timer = RoundTimer(self.__roundtime, self.round_over) self.__timer.start() def round_get_time_remaining(self): """ Returns the amount of time remaining in a tournament round or zero """ if self.__timer == None: return 0 return self.__timer.timeLeft def round_start(self): """ Called by GUI client to start a synchronized game start, or automatically when the game is initialized if auto_start = true Called every time a new round starts or once if a tournament is not being played. """ if not self.__started: logging.info("Starting Game") self.__started = True self.__autostart = self.__allowafterstart if self._tournament: logging.info("[Tournament] Round %d of %d", self._tournamentcurrentgroup+1, self._tournamentnumgroups) if not self._tournamentinitialized: logging.info("[Tournament] Initialized with %d players", len(self._players)) self._tournamentgroups = [] for x in xrange(self._tournamentnumgroups): self._tournamentgroups.append([]) #next x = 0 for player in self._players.values(): self._tournamentgroups[x % self._tournamentnumgroups].append(player) x += 1 #next self._tournamentinitialized = True elif self._tournamentcurrentgroup == self._tournamentnumgroups: logging.info("[Tournament] Final Round") self._tournamentfinalround = True #eif # we'll reset the world here so it's "fresh" and ready as soon as the game starts if self.__resetworldround: self._world_reset() #eif for player in self.game_get_current_player_list(): self._game_add_ship_for_player(player.netid, roundstart=True) #next self._round_start_timer() def player_added(self, player, reason): """ player_added is called in three separate cases 0) When the server first registers a ship on the server and adds them to the game 1) When a round starts and a player is added to the game round 2) When a player dies and is respawned in the world. cases 0 and 1 may occur one after another if auto_start is set to true. The ship object will have been created, but not added to the physics world yet, so no callback to world_add_remove_object may have occured yet. This is also before any commands are requested for the player (i.e. before the first environment is sent). You should add and initialize any new properties that you want on a player here. Reason values: _ADD_REASON_REGISTER_ = 0 _ADD_REASON_START_ = 1 _ADD_REASON_RESPAWN_ = 2 Analog to player_died """ if reason == 1: player.score = self._points_initial player.bestscore = self._points_initial player.deaths = 0 def player_get_start_position(self, force=False): """ Should return a position for a player to start at in the world. """ pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) x = 0 while len(self.world.getObjectsInArea(pos, 150)) > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) return pos def round_over(self): """ Called in a tournament when the round time expires. Will automatically advance the top players to the next round. """ if self.__timer != None: self.__timer.cancel() # if we get a round-over prematurely or do to some other condition, we should cancel this current timer self.__timer = None logging.debug("[Game] Round Over") self.game_update(0) # make sure we do one last update to sync and refresh the leaderboard cache # Get Stats for only the players in the round self.laststats = self.gui_get_player_stats() logging.info("[Game] Stats: %s", repr(self.laststats)) if len(self.laststats) > 0: if not self._tournamentfinalround: # get winner(s) for x in xrange(self.cfg.getint("Tournament", "number_to_final_round")): logging.info("Adding player to final round %s stats: %s", self.__leaderboard_cache[x].name, self.laststats[x]) self._player_add_to_final_round(self.__leaderboard_cache[x]) #next else: logging.info("Final Round Winner %s stats: %s", self._leader.name, self.laststats[0]) self._tournamentfinalwinner = self._leader #eif #eif for player in self._players: player.roundover = True if player.object != None: #player.object.destroyed = True self.world.remove(player.object) # overwritten by allowafterstart self.__autostart = self.cfg.getboolean("Game", "auto_start") # TODO: figure out a better way to tie these together if self._tournament: self.__autostart = False self._tournamentcurrentgroup += 1 logging.debug("[Tournament] Group Number Now %d", self._tournamentcurrentgroup) if not self.__autostart: # If not autostart, wait for next round to start self.__started = False else: self.round_start() def world_create(self, pys=True): """ Called by constructor to create world and when world reset, defaults to the standard world configuration from the config file definitions. """ return ConfiguredWorld(self, self.cfg, pys) def _world_reset(self): """ Recreates a world, called when the reset_world_each_round property is set. """ self.world.newWorld(self.world_create(False)) def round_get_has_started(self): """ Returns True when game has started. """ return self.__started def game_update(self, t): """ Called by the World to notify the passage of time The default game uses this update to cache the current leaderboard to be used by the GUI and the game to determine the leader info to send out. Note: This is called from the core game loop, best not to delay much here """ self.__leaderboard_cache = sorted(sorted(self.game_get_current_player_list(), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) if len(self.__leaderboard_cache) > 0: self._leader = self.__leaderboard_cache[0] self._highscore = getattr(self._leader, self._primary_victory) #eif #endregion #region Network driven functions def server_register_player(self, name, color, imgindex, netid, aiship=None): """ Called by the server when a new client registration message is received Has all information pertaining to a player entity Parameters: ship: Ship object - used to register AI Ships to the game world """ if not self._players.has_key(netid): if (self.__started and self.__allowafterstart) or not self.__started: # create new player # p = Player(name, color, imgindex, netid, None) self.player_added(p, BasicGame._ADD_REASON_REGISTER_) self._players[netid] = p if aiship != None: self.__aiships[netid] = aiship if self.__autostart: self._game_add_ship_for_player(netid, roundstart=True) logging.info("Registering Player: %s %d", name, netid) return True return False def _game_add_ship_for_player(self, netid, force=False, roundstart=False): """ Called internally when a player registers if autostart is true, or when round_start is called Also called when a player respawns. Look to override player_added instead. Also, sends initial environment to start RPC loop with client """ if netid < -2: # AI Ship self._players[netid].object = self.__aiships[netid] # reset AI Ship to look like new ship, though reuse object # TODO: this doesn't work, need 'new' object to add to world, or queue the add to occur after the remove...boo self.__aiships[netid].health.full() self.__aiships[netid].energy.full() self.__aiships[netid].shield.full() self.__aiships[netid].destroyed = False self.__aiships[netid].killed = False self.__aiships[netid].timealive = 0 self.__aiships[netid].body.velocity = Vec2d(0, 0) if not roundstart: # ai ship will be initialized with a starting position for round entry, but if killed, will randomize self.__aiships[netid].body.position = self.player_get_start_position(True) self._players[netid].object.ship_added() # tell AI ship to start else: self._players[netid].object = Ship(self.player_get_start_position(True), self.world) logging.info("Adding Ship for Player %d (%s) id #%d with Name %s", netid, repr(force), self._players[netid].object.id, self._players[netid].name) self._players[netid].object.player = self._players[netid] self._players[netid].object.owner = self._players[netid].object # Make ships owners of themselves for easier logic with ownership? self._players[netid].roundover = False if not self.cfg.getboolean("World", "collisions"): self._players[netid].object.shape.group = 1 if roundstart: self.player_added(self._players[netid], BasicGame._ADD_REASON_START_) else: self.player_added(self._players[netid], BasicGame._ADD_REASON_RESPAWN_) #eif self.world.append(self._players[netid].object) logging.info("Sending New Ship #%d Environment Info", self._players[netid].object.id) if netid >= 0 and not self._players[netid].waiting: self.server.sendEnvironment(self._players[netid].object, netid) return self._players[netid].object def game_get_info(self): """ Called when a client is connected and appends this data to the default image number, world width and world height Should at least return a key "GAMENAME" with the name of the game module. Your game name should be "Basic" if you don't want to return anything in game_get_extra_environment to the player besides Score, Bestscore, Deaths, Highscore, Time left, and Round time. Note: The client doesn't expose a way to get anything besides the game name right now. """ return {"GAMENAME": "Basic"} def game_get_extra_environment(self, player): """ Called by World to return extra environment info to a player when they need to return a command (for just that player). If you desire to return more information to the client, you can create an extended BasicGameInfo class there and repackage the jar. See info in the server docs on adding a subgame. """ return {"SCORE": player.score, "BESTSCORE": player.bestscore, "DEATHS": player.deaths, "HIGHSCORE": self._highscore, "TIMELEFT": int(self.round_get_time_remaining()), "ROUNDTIME": self.__roundtime} def game_get_extra_radar_info(self, obj, objdata): """ Called by the World when the obj is being radared, should add new properties to the objdata. Note: it is not supported to pick up these values directly by the client, it is best to use the existing properties that make sense. """ if hasattr(obj, "player") and self.cfg.getboolean("World", "radar_include_name"): objdata["NAME"] = obj.player.name def server_disconnect_player(self, netid): """ Called by the server when a disconnect message is received by a player's client player_died should also be called, so it is recommended to override that method instead. """ for player in self._players: if player.netid == netid and player.object != None: logging.debug("Player %s Disconnected", player.name) player.disconnected = True player.object.killed = True # terminate self.world.remove(player.object) player.object = None if self.__allowreentry: if self._players.has_key(player.netid): del self._players[player.netid] # should be handled by world part return True logging.debug("Couldn't find player with netid %d to disconnect", netid); return False def player_get_by_name(self, name): """ Retrieves a player by their name. """ for player in self._players.values(): if player.name == name: return player return None def player_get_by_network_id(self, netid): """ Used by the server to retrieve a player by their network id. """ if self._players.has_key(netid): return self._players[netid] return None #endregion #region Player Scoring / Leaderboard Functions def player_update_score(self, player, amount): """ Should be called to manipulate a player's score, will do extra bookkeeping and sanity for you. """ player.score += amount # scores can't be negative if player.score < 0: player.score = 0 # TODO: should probably check if primary highest flag to see if we want to keep track of lowest or highest score here # update if this is a new personal best if player.score > player.bestscore: player.bestscore = player.score def player_died(self, player, gone): """ Will be called when a player dies, will adjust score appropriately based on game rules. Called before player object removed from player. gone will be True if the player is disconnected or killed (they won't respawn) Analog to player_added """ player.deaths += 1 if self._points_lost_on_death > 0: self.player_update_score(player, -self._points_lost_on_death) if self._reset_score_on_death: self._player_reset_score(player) def _player_reset_score(self, player): """ Used to reset a players score and determine the new leader. Will be called by the default implementation of player_died if the reset_score_on_death configuration property is true. """ player.score = self._points_initial def _player_add_to_final_round(self, player): """ Add the player to the final round. Will be automatically called by the default implementation for round_over. """ self._tournamentfinalgroup.append(player) def game_get_current_leader_list(self, all=False): """ Gets the list of players sorted by their score (highest first) (or all players) """ # secondary victory first, primary second if all: return sorted(sorted(self.game_get_current_player_list(all), key=attrgetter(self._secondary_victory), reverse=self._secondary_victory_high), key=attrgetter(self._primary_victory), reverse=self._primary_victory_high) # TODO: Cache this and regen, update leader and highscore value there too, should I do this once every game update? return self.__leaderboard_cache def game_get_current_player_list(self, all=False): """ Returns a list of player objects for players in the current round Returns all players if no tournament running or requested """ if all or not self._tournament: return self._players else: if self._tournamentfinalround: return self._tournamentfinalgroup else: if self._tournamentcurrentgroup < len(self._tournamentgroups): return self._tournamentgroups[self._tournamentcurrentgroup] else: return [] #eif #eif def player_get_stat_string(self, player): """ Should return a string with information about the player and their current score. Defaults to: primary_score secondary_score : player_name """ return "%.1f" % getattr(player, self._primary_victory) + " " + str(getattr(player, self._secondary_victory)) + " : " + player.name def tournament_is_running(self): """ Returns true if a tournament is running. """ return self._tournament #endregion #region World/Collision Functions def world_add_remove_object(self, wobj, added): """ Called by world when an object is added or destroyed (before added (guaranteed to not have update) and after removed (though may receive last update)) For simple tasks involving players look to the player_died or player_added methods killed ships will not return (used to prevent respawn) """ logging.debug("[Game] Add Object(%s): #%d (%s)", repr(added), wobj.id, friendly_type(wobj)) if not added and isinstance(wobj, Ship) and wobj.player.netid in self._players: nid = wobj.player.netid # if we were given an expiration time, means we haven't issued a command, so kill the ship if wobj.has_expired() and self.cfg.getboolean("Server", "disconnect_on_idle"): logging.info("Ship #%d killed due to timeout.", wobj.id) wobj.killed = True self.player_died(self._players[nid], (self._players[nid].disconnected or wobj.killed)) self._players[nid].object = None if not self._players[nid].disconnected: if self._disconnect_on_death or wobj.killed: if self.__allowreentry: del self._players[nid] # TODO: disconnect AI? if nid >= 0: self.server.sendDisconnect(nid) else: if not self._players[nid].roundover: # if the round isn't over, then re-add the ship self._game_add_ship_for_player(nid, True) def world_physics_pre_collision(self, shapes): """ Called by the physics engine when two objects just touch for the first time return [True/False, [[func, obj, para...]... ] ] use False to not process collision in the physics engine, the function callback will still be called The default game prevents anything from colliding with BlackHole or Nebula. shapes is a list of pymunk Shape classes, but you can look at it's id or world_object properties to get useful information. """ for shape in shapes: if isinstance(shape.world_object, BlackHole) or isinstance(shape.world_object, Nebula): return [ False, [] ] def world_physics_collision(self, shapes, damage): """ Called by the physics engine when two objects collide Return [[func, obj, para...]...] The default game handles inflicting damage on entities in this step. It is best to override world_physics_pre_collision if you want to prevent things from occuring in the first place. """ r = [] for shape in shapes: #print shape.id, ":", gobj = shape.world_object if isinstance(gobj, Ship) and gobj.commandQueue.containstype(RaiseShieldsCommand) and gobj.shield.value > 0: gobj.shield -= damage * gobj.shieldDamageReduction gobj.health -= damage * (1.0 - gobj.shieldDamageReduction) else: logging.debug("Object #%d took %d damage", gobj.id, damage) gobj.health -= damage #eif if gobj.health.maximum > 0 and gobj.health.value <= 0: gobj.killedby = None if len(shapes) == 2: for s2 in shapes: if s2 != shape: gobj.killedby = self.world[s2.id] break logging.info("Object #%d destroyed by %s", gobj.id, repr(gobj.killedby)) r.append([self.world_physics_post_collision, gobj, damage]) if isinstance(gobj, Ship): gobj.player.sound = "EXPLODE" elif isinstance(gobj, Ship): gobj.player.sound = "HIT" #eif if r == []: return None return r def world_physics_post_collision(self, dobj, para): """ Setup and called by world_physics_collision to process objects which have been destroyed as a result of taking too much damage. The default game causes an explosion force based on the strength of the collision in the vicinity of the collision. dobj: the object destroyed para: extra parameters from a previous step, by default collision passes the strength of the collision only """ strength = para[0] / 75.0 logging.info("Destroying Object: #%d, Force: %d [%d]", dobj.id, strength, thread.get_ident()) dobj.destroyed = True self.world.causeExplosion(dobj.body.position, dobj.radius * 5, strength / 75.0, True) #Force in physics step self.world.remove(dobj) #endregion #region GUI Drawing def gui_initialize(self): """ Used to initialize GUI resources at the appropriate time after the graphics engine has been initialized. """ self._tfont = pygame.font.Font("freesansbold.ttf", 18) self._dfont = debugfont() def gui_draw_game_world_info(self, surface, flags): """ Called by GUI to have the game draw additional (optional) info in the world when toggled on 'G'. (coordinates related to the game world) """ pass def gui_draw_game_screen_info(self, screen, flags): """ Called by GUI to have the game draw additional (optional) info on screen when toggled on 'G'. (coordinates related to the screen) """ pass def gui_get_player_stats(self, all=False): """ Called by GUI to get the sorted list of player stats. GUI expects a list of strings, you should usually override player_get_stat_string. """ sstat = [] for player in self.game_get_current_leader_list(all): sstat.append(self.player_get_stat_string(player)) return sstat def gui_draw_tournament_bracket(self, screen, flags): """ Called by GUI to draw info about the round/tournament (optional) when toggled on 'T'. (coordinates related to the screen) """ if self._tournament and self._tournamentinitialized: # draw first Bracket y = 100 for x in xrange(self._tournamentnumgroups): c = (128, 128, 128) if x == self._tournamentcurrentgroup: c = (255, 255, 255) py = y for player in self._tournamentgroups[x]: screen.blit(self._tfont.render(player.name, False, c), (100, y)) y += 24 # draw bracket lines pygame.draw.line(screen, (192, 192, 192), (400, py), (410, py)) pygame.draw.line(screen, (192, 192, 192), (410, py), (410, y)) pygame.draw.line(screen, (192, 192, 192), (400, y), (410, y)) pygame.draw.line(screen, (192, 192, 192), (410, py + (y - py) / 2), (410, py + (y - py) / 2)) y += 36 # draw Final Bracket y = 96 + ((y - 96) / 2) - len(self._tournamentfinalgroup) * 16 py = y for player in self._tournamentfinalgroup: screen.blit(self._tfont.render(player.name, False, (255, 255, 128)), (435, y)) y += 24 pygame.draw.line(screen, (192, 192, 192), (800, py), (810, py)) pygame.draw.line(screen, (192, 192, 192), (810, py), (810, y)) pygame.draw.line(screen, (192, 192, 192), (800, y), (810, y)) if self._tournamentfinalwinner: screen.blit(self._tfont.render(self._tournamentfinalwinner.name, False, (128, 255, 255)), (835, py + (y - py) / 2))
class DiscoveryQuestGame(BasicGame): """ Discovery Quest is a game of exploration. Ships must 'Scan' various objects in order to accumulate points. Every type of object in the game is worth a different number of points. Scanning is different from Radar, and requires a precise ID and takes more time and energy to perform. It also has a limited range which you must stay within for the whole duration of the scan. You CANNOT scan the same object more than once for points. Ships must establish an 'Outpost' by scanning it. Once they have done so, they will start to receive 'Missions'. They initially won't score points for scanned objects until an Outpost is established. All points for things scanned before making an Outpost will be awarded when an Outpost is established. A Mission dictates which objects a ship should go scan. If a ship goes and scans ONLY those things, bonus points will be awarded when they return to their outpost and Scan it again. Scanning their outpost will always give them a new mission. Your Ship being destroyed clears/fails your mission, so you must return to your established outpost if you want a new one. """ def __init__(self, cfgobj): self._outposts = ThreadSafeDict() super(DiscoveryQuestGame, self).__init__(cfgobj) self.mustbase = cfgobj.getboolean("DiscoveryQuest", "establish_homebase") self.usemissions = cfgobj.getboolean("DiscoveryQuest", "missions") self._missions = cfgobj.get("DiscoveryQuest", "mission_objectives").split(",") self.scantime = cfgobj.getfloat("DiscoveryQuest", "scan_time") self.scanrange = cfgobj.getint("DiscoveryQuest", "scan_range") self.scanduration = cfgobj.getint("DiscoveryQuest", "scan_duration") self.outpostdist = cfgobj.getint("DiscoveryQuest", "ship_spawn_dist") self.limitwarp = cfgobj.getboolean("DiscoveryQuest", "disable_warp_in_nebula") def game_get_info(self): return {"GAMENAME": "DiscoveryQuest"} def player_reset_mission(self, player): if self.usemissions: # get a number of missions for each thing player.mission = [] player.scanned = [] player.failed = False for i in xrange(cfg_rand_min_max(self.cfg, "DiscoveryQuest", "mission_num")): player.mission.append(random.choice(self._missions)) # TODO: validate that this player can still scan those types of objects def player_added(self, player, reason): if reason == BasicGame._ADD_REASON_REGISTER_: player.buffervalue = 0 # stored points before scanning first output, if outpost is required. player.outpost = None # home base obj player.lastids = [] # last ids scanned player.scantimes = {} # timers for scans, id: timestamp player.mission = [] # List of strings of items to scan player.scanned = [] # ids scanned by player player.failed = True # whether current mission has failed, starts as true as no mission to start return super(DiscoveryQuestGame, self).player_added(player, reason) def player_get_start_position(self, force = False): # pick a random outpost to spawn the player besides out = intpos(random.choice(self._outposts.values()).body.position) pos = (random.randint(out[0]-self.outpostdist, out[0]+self.outpostdist), random.randint(out[1]-self.outpostdist, out[1]+self.outpostdist)) x = 0 while len(self.world.getObjectsInArea(pos, self.outpostdist / 2)) > 0 and x < 15: x += 1 pos = (random.randint(out[0]-self.outpostdist, out[0]+self.outpostdist), random.randint(out[1]-self.outpostdist, out[1]+self.outpostdist)) return pos def player_died(self, player, gone): if gone and player.outpost != None: player.outpost.home_for.remove(player) player.outpost = None if gone: for obj in self.world: if player in obj.scanned_by: obj.scanned_by.remove(player) return super(DiscoveryQuestGame, self).player_died(player, gone) def player_get_stat_string(self, player): """ Should return a string with information about the player and their current score. Defaults to: primary_score secondary_score : player_name """ if player.buffervalue > 0: return "%d %s B: %d" % (getattr(player, self._primary_victory), player.name, getattr(player, self._secondary_victory)) else: return "%d %s" % (getattr(player, self._primary_victory), player.name) def world_add_remove_object(self, wobj, added): if isinstance(wobj, Outpost): if not added: del self._outposts[wobj.id] else: self._outposts[wobj.id] = wobj if added: wobj.scanned_by = [] # objects keep track of who scans them # give object point value opt = "points_" + friendly_type(wobj).lower() if self.cfg.has_option("DiscoveryQuest", opt): wobj.value = self.cfg.getint("DiscoveryQuest", opt) else: #guard wobj.value = 0 else: # clean-up reference to scantime on obj death for player in self.game_get_current_player_list(): if player in wobj.scanned_by: del player.scantimes[wobj.id] return super(DiscoveryQuestGame, self).world_add_remove_object(wobj, added) def game_get_extra_environment(self, player): env = super(DiscoveryQuestGame, self).game_get_extra_environment(player) if player.outpost != None: env["POSITION"] = intpos(player.outpost.body.position) env["FAILED"] = player.failed env["MISSION"] = player.mission obj = player.object if obj != None: cur = [] for cmd in obj.commandQueue: if isinstance(cmd, ScanCommand): cur.append(cmd.target) env["CURIDS"] = cur env["SUCIDS"] = player.lastids player.lastids = [] return env def game_get_extra_radar_info(self, obj, objdata, player): super(DiscoveryQuestGame, self).game_get_extra_radar_info(obj, objdata, player) # return object's value for scanning if hasattr(obj, "value"): objdata["VALUE"] = obj.value objdata["SUCCESS"] = player in obj.scanned_by def server_process_network_message(self, ship, cmdname, cmddict={}): """ Called by the server when it doesn't know how to convert a network message into a Ship Command. This function is used to create commands custom to the game itself. Parameters: ship: Ship object which will process the Command cmdname: string network shortcode for command, set overriding getName in ShipCommand on Client cmddict: dictionary of properties of a command, set using private fields on ShipCommand class Return: return a server 'Command' object, a string indicating an error, or None """ if cmdname == GAME_CMD_SCAN: if cmddict.has_key("TARGET") and isinstance(cmddict["TARGET"], int) and cmddict["TARGET"] > 0: return ScanCommand(ship, self, cmddict["TARGET"]) else: return "Discovery Quest Scan Command requires a target id." def server_process_command(self, ship, command): # Discovery Quest prevents warp in a nebula if self.limitwarp and isinstance(command, WarpCommand): for body in ship.in_celestialbody: if isinstance(body, Nebula): logging.info("Preventing Ship #%d From Warping As In Nebula", ship.id) return "Discovery Quest Rule - Can't warp when in Nebula" return command def dq_finished_scan(self, ship, obj, success): if obj != None: logging.info("Ship #%d finished scan of object #%d and was successful? [%s]", ship.id, obj.id, repr(success)) else: logging.info("Ship #%d finished scan of unknown object (never saw id)", ship.id) if success: ship.player.lastids.append(obj.id) if isinstance(obj, Outpost) and (ship.player in obj.home_for or ship.player.outpost == None): # scanned player's own Outpost, do Mission Stuff HERE if ship.player.outpost == None: # establish as ship's Outpost ship.player.outpost = obj obj.home_for.append(ship.player) ship.player.update_score(ship.player.buffervalue + obj.value * 2) # Score initial points + 2*outpost value for establishing base ship.player.buffervalue = 0 ship.player.sound = "SUCCESS" if self.usemissions and not ship.player.failed and len(ship.player.mission) == 0: # completed mission exactly points = 0 # tally points of scanned objects for obj in ship.player.scanned: points += obj.value ship.player.sound = "MISSION" ship.player.update_score(points * self.cfg.getfloat("DiscoveryQuest", "mission_bonus_multiplier")) self.player_reset_mission(ship.player) elif ship.player in obj.scanned_by: logging.info("Ship #%d has ALREADY scanned object #%d", ship.id, obj.id) else: # mission checks if friendly_type(obj) in ship.player.mission: ship.player.mission.remove(friendly_type(obj)) else: ship.player.failed = True ship.player.scanned.append(obj) # track obj scan ship.player.scantimes[obj.id] = 0 obj.scanned_by.append(ship.player) # update scores ship.player.sound = "SUCCESS" if ship.player.outpost != None or not self.mustbase: # or we don't require bases for points ship.player.update_score(obj.value) else: #haven't found outpost, need to buffer points ship.player.buffervalue += obj.value #end dq_finished_scan def game_update(self, t): super(DiscoveryQuestGame, self).game_update(t) # check timeout of scan duration (if enabled) if self.scanduration > 0: for player in self.game_get_current_player_list(): for id in player.scantimes.keys(): player.scantimes[id] += t if player.scantimes[id] >= self.scanduration: obj = self.world[id] obj.scanned_by.remove(player) del player.scantimes[id] #end game_update def gui_draw_game_world_info(self, surface, flags, trackplayer): for player in self.game_get_current_player_list(): obj = player.object if obj != None: bp = intpos(obj.body.position) wrapcircle(surface, (0, 255, 255), bp, self.scanrange, self.world.size, 1) # Scan Range if self.usemissions: text = debugfont().render("%s [%s]" % (repr(player.mission), player.failed), False, (0, 255, 255)) surface.blit(text, (bp[0]-text.get_width()/2, bp[1] - 6)) if trackplayer != None: curs = [] curf = [] obj = trackplayer.object if obj != None: # Draw Success/Failure Circles around Current Scan Targets for cmd in obj.commandQueue: if isinstance(cmd, ScanCommand): if cmd.success: obj = self.world[cmd.target] wrapcircle(surface, (255, 255, 0), intpos(obj.body.position), obj.radius + 6, self.world.size, 2) else: obj = self.world[cmd.target] wrapcircle(surface, (255, 0, 0), intpos(obj.body.position), obj.radius + 6, self.world.size, 2) # Draw Circles around scanned entities for id, scantime in trackplayer.scantimes.iteritems(): obj = self.world[id] #if trackplayer in obj.scanned_by: if self.scanduration > 0: c = 160 * (scantime / self.scanduration) else: c = 0 wrapcircle(surface, (0, 255 - c, 255 - c), intpos(obj.body.position), obj.radius + 4, self.world.size, 4)
class GameWorld(object): """ represents the virtual space world and the objects in it Provides hit detection and radar between objects in the world """ def __init__(self, game, worldsize, objlistener=None): """ Parameters: game: game object that manages rules for world worldsize: tuple of world width,height pys: flag to indicate whether this should be the main world running a physics/game loop or not objlistener: initial listner callback function """ self.__game = game self.size = intpos(worldsize) self.width = worldsize[0] self.height = worldsize[1] self.__space = pymunk.Space() #self.__space.add_collision_handler(0, 0, post_solve=self.collideShipStellarObject) self.__space.set_default_collision_handler( begin=self.__beginCollideObject, post_solve=self.__collideObject, separate=self.__endCollideObject) self.__objects = ThreadSafeDict() self.__addremovesem = threading.Semaphore() self.__influential = [] if objlistener == None: self.__objectListener = [] else: self.__objectListener = [objlistener] self.__toremove = [] self.__toadd = [] self.__active = False self.__started = False self.gameerror = False logging.info("Initialized World of size %s", repr(worldsize)) def start(self): if not self.__started: self.__started = True self.__active = True self.__pys = True logging.debug("Started gameloop for World %s", repr(self)) threading.Thread(None, self.__THREAD__gameloop, "WorldMap_gameloop_" + repr(self)).start() def mid_point(self, xoff=0, yoff=0): return intpos((self.width / 2 + xoff, self.height / 2 + yoff)) def endGameLoop(self): self.__active = False def destroy_all(self): self.__pys = False # disable physics step while we add/remove all objects time.sleep(0.2) # Remove All Objects for obj in self: self.pysremove(obj) self.__pys = True def __beginCollideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_pre_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object) if r != None: if r == False or r == True: return r for i in r[1:]: space.add_post_step_callback(i[0], i[1], i[2]) return r[0] return True def __collideObject(self, space, arbiter): if arbiter.is_first_contact: r = self.__game.world_physics_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object, arbiter.total_impulse.length / 250.0) if r != None: for i in r: space.add_post_step_callback(i[0], i[1], i[2]) #eif def __endCollideObject(self, space, arbiter): # when object is destroyed in callback, arbiter may be empty if hasattr(arbiter, "shapes"): self.__game.world_physics_end_collision( arbiter.shapes[0].world_object, arbiter.shapes[1].world_object) def causeExplosion(self, origin, radius, strength, force=False): logging.debug("Start Explosion %s, %d, %d [%d]", origin, radius, strength, thread.get_ident()) origin = pymunk.Vec2d(origin) for obj in self.__objects: if obj.explodable: for point in wrappos(obj.body.position, radius, self.size): if in_circle(origin, radius, point): logging.debug( "Applying Explosion Force of strength %d impulse %s to #%d", strength, repr( (point - origin) * strength), obj.id) obj.body.apply_impulse((point - origin) * strength, (0, 0)) break logging.debug("Done Explosion") def registerObjectListener(self, callback): if callback not in self.__objectListener: self.__objectListener.append(callback) def __len__(self): return len(self.__objects) def __iter__(self): return self.__objects.__iter__() def __getitem__(self, key): return self.__objects.__getitem__(key) def __setitem__(self, key, value): return self.__objects.__setitem__(key, value) def has_key(self, key): return self.__objects.has_key(key) def append(self, item, pys=False): """ Call to add an object to the game world from threads outside the game loop """ logging.debug("Append Object to World %s [%d]", repr(item), thread.get_ident()) for objListener in self.__objectListener: objListener(item, True) logging.debug("SEMAPHORE ACQ append [%d]", thread.get_ident()) self.__addremovesem.acquire() if not self.__objects.has_key(item.id): self.__objects[item.id] = item if pys: item.addToSpace(self.__space) elif item in self.__toremove: self.__toremove.remove(item) else: self.__toadd.append(item) if isinstance(item, Influential) and item.influence_range > 0: self.__influential.append(item) self.__addremovesem.release() logging.debug("SEMAPHORE REL append [%d]", thread.get_ident()) def remove(self, item): """ Call to remove an object from the game world """ del self[item] def pysremove(self, item): self.__delitem__(item, True) def __delitem__(self, key, pys=False): logging.debug("Removing Object from World %s [%d]", repr(key), thread.get_ident()) # Notify each item this may be in that it's no longer colliding # HACK: Get around issue with PyMunk not telling us shapes when object removed already before separate callback if len(key.in_celestialbody) > 0: for item in key.in_celestialbody[:]: item.collide_end(key) logging.debug("SEMAPHORE ACQ delitem [%d]", thread.get_ident()) self.__addremovesem.acquire() if self.__objects.has_key(key.id): if pys: key.removeFromSpace(self.__space) elif key in self.__toadd: self.__toadd.remove(key) else: self.__toremove.append(key) del self.__objects[key.id] if key in self.__influential: self.__influential.remove(key) self.__addremovesem.release() logging.debug("SEMAPHORE REL delitem [%d]", thread.get_ident()) # Notify after removed, in case re-add same object for objListener in self.__objectListener: objListener(key, False) def __THREAD__gameloop(self): lasttime = MINIMUM_GAMESTEP_TIME try: while self.__active: if self.__pys: self.__space.step( MINIMUM_GAMESTEP_TIME) # Advance Physics Engine tstamp = time.time() # update all game objects for obj in self: # self is dictionary # Wrap Object in World if obj.body.position[0] < 0: obj.body.position[0] += self.width elif obj.body.position[0] > self.width: obj.body.position[0] -= self.width if obj.body.position[1] < 0: obj.body.position[1] += self.height elif obj.body.position[1] > self.height: obj.body.position[1] -= self.height # Apply Gravity for Planets if obj.gravitable: for influencer in self.__influential: for point in wrappos(obj.body.position, influencer.influence_range, self.size): if in_circle(influencer.body.position, influencer.influence_range, point): influencer.apply_influence( obj, point, lasttime) break # Update and Run Commands obj.update(lasttime) if obj.has_expired() or obj.destroyed: del self[obj] #next # game time notification self.__game.game_update(lasttime) # update time lasttime = time.time() - tstamp logging.debug("SEMAPHORE ACQ gameloop [%d]", thread.get_ident()) self.__addremovesem.acquire() # NOTE ISSUE #68 - If server is slow, can be adding and removing object in same step... add first, so we can immediately remove instead of crash # TODO: just look for common set between two lists and remove... or have each list check the other first before adding to the lists... # probably best to refactor to PyMunk 4.0.0 and be able to have PyMunk handle adding/removing objects during physics step. # add objects for item in self.__toadd: logging.info("World Adding #%d to Physics Engine %s", item.id, repr(item)) item.addToSpace(self.__space) self.__toadd = [] #remove objects for item in self.__toremove: logging.info("World Removing #%d from Physics Engine %s", item.id, repr(item)) item.removeFromSpace(self.__space) self.__toremove = [] self.__addremovesem.release() logging.debug("SEMAPHORE REL gameloop [%d]", thread.get_ident()) # minimum to conserve resources if lasttime < MINIMUM_GAMESTEP_TIME: time.sleep(MINIMUM_GAMESTEP_TIME - lasttime) lasttime = MINIMUM_GAMESTEP_TIME #else: #logg#ging.debug("Can't keep at 50fps at %dfps", 1.0 / lasttime) #eif #wend except: print "EXCEPTION IN GAMELOOP" logging.exception("FATAL Error in game loop!!!") logging.info(traceback.format_exc()) logging.error(traceback.format_exc()) print traceback.format_exc() self.gameerror = True logging.debug("Gameloop Ended") def getObjectsInArea(self, pos, radius, radar=False): """ Returns objects within the given radius from the position (even wrapping around edge of world), pass radar = True if for environment """ logging.debug("Get Objects In Area %s %d [%d]", repr(pos), radius, thread.get_ident()) objList = [] for obj in self.__objects.values(): for point in wrappos(obj.body.position, radius, self.size): if in_circle(pos, radius, point): objList.append(obj) break #eif #next #next return objList def get_count_of_objects(self, type): tname = type.__name__ count = 0 for obj in self.__objects.values(): if obj.__class__.__name__ == tname: count += 1 #eif #next return count def getObjectData(self, obj, player): #TODO: Move these properties to the 'getExtraInfo' of the base Entity and have child classes call super... objData = {} # Convert Type of Object to String objData["TYPE"] = friendly_type(obj) objData["ID"] = obj.id objData["POSITION"] = intpos(obj.body.position) objData["SPEED"] = obj.body.velocity.length # TODO: deal with -0.0 case OR match physics library coordinates? objData[ "DIRECTION"] = -obj.body.velocity.angle_degrees % 360 # 30 = -120?, -80 = -10 #objData["VELOCITYDIRECTION"] = obj.velocity.direction objData["MAXSPEED"] = obj.body.velocity_limit objData["CURHEALTH"] = obj.health.value objData["MAXHEALTH"] = obj.health.maximum objData["CURENERGY"] = obj.energy.value objData["MAXENERGY"] = obj.energy.maximum objData["ENERGYRECHARGERATE"] = obj.energyRechargeRate objData["MASS"] = obj.mass objData[ "HITRADIUS"] = obj.radius #TODO: Move to entites that have this i.e. physicalround? objData["TIMEALIVE"] = obj.timealive objData["INBODY"] = (len(obj.in_celestialbody) > 0) obj.getExtraInfo(objData, player) self.__game.game_get_extra_radar_info(obj, objData, player) return objData def getEnvironment(self, ship, radarlevel=0, target=-1, getMessages=False): #TODO: abstract radar to game level? radardata = None if radarlevel > 0: objs = self.getObjectsInArea(ship.body.position, ship.radarRange, True) #TODO: Need to wait lock the removing of ships with accessing...??? if ship in objs: objs.remove(ship) # Get rid of self... # remove ships with cloak for x in xrange(len(objs) - 1, -1, -1): if isinstance(objs[x], Ship) and objs[x].commandQueue.containstype( CloakCommand): del objs[x] #eif #next if radarlevel == 1: # number of objects radardata = [] for i in xrange(len(objs)): radardata.append({}) elif radarlevel == 2: # (pos, id) list radardata = [] for e in objs: radardata.append({ "POSITION": intpos(e.body.position), "ID": e.id }) #next elif radarlevel == 3: for obj in objs: if obj.id == target: radardata = [self.getObjectData(obj, ship.player)] break elif radarlevel == 4: # (pos, id, type) radardata = [] for e in objs: radardata.append({ "POSITION": intpos(e.body.position), "ID": e.id, "TYPE": friendly_type(e) }) #next elif radarlevel == 5: radardata = [] for e in objs: radardata.append(self.getObjectData(e, ship.player)) #next #eif #eif msgQueue = [] if getMessages: msqQueue = list(ship.messageQueue) return { "SHIPDATA": self.getObjectData(ship, ship.player), "RADARLEVEL": radarlevel, "RADARDATA": radardata, "GAMEDATA": self.__game.game_get_extra_environment(ship.player), "MESSAGES": msgQueue, }
def startGame(windowcaption, game, fullscreen=True, resolution=None, showstats=False, sound=False, testcase=None): #region Initialization logging.info("Initiating PyGame...") world = game.world pygame.init() fpsClock = pygame.time.Clock() logging.debug("Initiating Screen...") if sound: logging.info("Starting Sound...") pygame.mixer.init() SCache(True) else: logging.info("Sound Disabled") SCache(False) ipaddress = getIPAddress() if resolution == None: resolution = pygame.display.list_modes()[0] windowSurface = pygame.display.set_mode(resolution, (pygame.FULLSCREEN * fullscreen) | pygame.HWSURFACE | pygame.DOUBLEBUF) pygame.display.set_caption(windowcaption) logging.debug("Game GUI CFG...") # Let game initialize any GUI objects independently game.gui_initialize() colorBlack = pygame.Color(0, 0, 0) #shipw = ShipGUI(Ship((100, 100))) #shipw.ship.velocity.magnitude = 5 #region World Registration bgobjects = ThreadSafeDict() # items we want always in the background objects = ThreadSafeDict() shipids = [] trackshipid = None def addorremove(obj, added): logging.debug("GUI: Add/Remove Obj %s (%s) [%d]", repr(obj), repr(added), thread.get_ident()) try: if added: # TODO: #18 - Clean-up this by auto creating wrapper on name... if isinstance(obj, Ship): logging.debug("GUI: Adding Ship #%d", obj.id) objects[obj.id] = ShipGUI(obj, world) shipids.append(obj.id) logging.debug("GUI: Added Ship #%d", obj.id) elif isinstance(obj, Nebula): logging.debug("GUI: Adding Nebula #%d", obj.id) bgobjects[obj.id] = NebulaGUI(obj, world) logging.debug("GUI: Added Nebula #%d", obj.id) elif isinstance(obj, Planet): logging.debug("GUI: Adding Planet #%d", obj.id) bgobjects[obj.id] = PlanetGUI(obj, world) logging.debug("GUI: Added Planet #%d", obj.id) elif isinstance(obj, Asteroid): logging.debug("GUI: Adding Asteroid #%d", obj.id) objects[obj.id] = AsteroidGUI(obj, world) logging.debug("GUI: Added Asteroid #%d", obj.id) elif isinstance(obj, Torpedo): logging.debug("GUI: Adding Torpedo #%d", obj.id) objects[obj.id] = TorpedoGUI(obj, world) logging.debug("GUI: Added Torpedo #%d", obj.id) else: logging.debug("GUI: Adding %s #%d", repr(obj), obj.id) objects[obj.id] = obj.WRAPPERCLASS(obj, world) logging.debug("GUI: Added %s %%d", repr(obj), obj.id) #eif else: if isinstance(obj, Ship): logging.debug("GUI: Ship #%d Dying", obj.id) ship = objects.get(obj.id, None) if ship != None: ship.dying = True logging.debug("GUI: Ship #%d Set Dying", obj.id) elif obj.id in objects: logging.debug("GUI: Removing Object #%d", obj.id) del objects[obj.id] logging.debug("GUI: Removed Object #%d", obj.id) elif obj.id in bgobjects: logging.debug("GUI: Removing BG Object #%d", obj.id) del bgobjects[obj.id] logging.debug("GUI: Removed BG Object #%d", obj.id) #eif #eif except: logging.error("GUI ERROR") logging.error(traceback.format_exc()) print traceback.format_exc() #logging.debug("GUI: Add/Remove Done [%d]", thread.get_ident()) #endregion logging.debug("Register World Listener...") world.registerObjectListener(addorremove) logging.debug("Add Objects to GUI...") # Create objects for all items in world for obj in world: addorremove(obj, True) #efor # Create Stars #stars = [] #for i in xrange((world.width / STAR_DENSITY * world.height / STAR_DENSITY)): # stars.append(Star((random.randint(4, world.width - 4), random.randint(4, world.height - 4)))) logging.debug("Create Fonts...") # Start Main Loop font = pygame.font.Font("freesansbold.ttf", 12) bigfont = pygame.font.Font("freesansbold.ttf", 44) logging.debug("Load Textures...") spacetexture = Cache().getImage("Backgrounds/space") scalefactorx = float(world.size[0]) / resolution[0] scalefactory = float(world.size[1]) / resolution[1] offsetx = 0 maxoffsetx = -world.width + resolution[0] offsety = 0 maxoffsety = -world.height + resolution[1] def centerViewOnWorld(pos): return -pos[0] + resolution[0]/2, -pos[1] + resolution[1]/2 #keyboard repeat rate pygame.key.set_repeat(75, 25) zoomout = True mouselock = True MOUSELOCK_RANGE = resolution[0] / 8 logintercept = False mlh = MessageLogHandler(50) dynamiccamera = False mousemode = None prevzoom = zoomout showip = showstats showplayerlist = showstats showroundtime = game.cfg.getboolean("Tournament", "tournament") tournamentinfo = game.cfg.getboolean("Tournament", "tournament") flags = {"DEBUG":False, "STATS":showstats, "NAMES":True, "GAME":showstats} logging.info("Create Main Surface...") #TODO: Optimize by only drawing objects in viewport... worldsurface = pygame.Surface((world.size[0], world.size[1]), flags=HWSURFACE) logging.debug("Game Start!") obj = None notexit = True # In test case we only care about test being done if testcase != None: notexit = False #endregion while notexit or (testcase != None and not testcase.donetest): t = pygame.time.get_ticks() / 1000.0 #for star in stars: # star.draw(windowSurface) # TODO: figure out abstraction for pygame or method in order to get objwrapper to figure out if it needs # to draw in the screen or be no-op, ideally objwrapper just does what it does now... worldsurface.fill(colorBlack) #for x in xrange(offsetx, resolution[0], 512): # for y in xrange(offsety, resolution[1], 512): # worldsurface.blit(spacetexture, (x, y)) # draw background items first in specific z-order for obj in sorted(bgobjects.values(), key=attrgetter('zorder')): obj.draw(worldsurface, flags) if obj.dead: del bgobjects[obj._worldobj.id] # draw active objects for obj in objects: obj.draw(worldsurface, flags) if hasattr(obj._worldobj, "player") and obj._worldobj.player.sound != None: #print "Trying to play sound", obj._worldobj.player.sound SCache().play(obj._worldobj.player.sound) obj._worldobj.player.sound = None if obj.dead: del objects[obj._worldobj.id] if isinstance(obj, ShipGUI): shipids.remove(obj._worldobj.id) if flags["GAME"]: game.gui_draw_game_world_info(worldsurface, flags) #region Key/Mouse Event Handling for event in pygame.event.get(): if event.type == QUIT: notexit = False elif event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.event.post(pygame.event.Event(QUIT)) elif event.key == K_SPACE and not game.round_get_has_started(): game.round_start() elif event.key == K_d: flags["DEBUG"] = not flags["DEBUG"] elif event.key == K_i: showip = not showip elif event.key == K_p: showplayerlist = (showplayerlist + 1) % (2 + game.tournament_is_running()) elif event.key == K_s: flags["STATS"] = not flags["STATS"] elif event.key == K_n: flags["NAMES"] = not flags["NAMES"] elif event.key == K_UP: offsety += 16 elif event.key == K_DOWN: offsety -= 16 elif event.key == K_RIGHT: offsetx -= 16 elif event.key == K_LEFT: offsetx += 16 elif event.key == K_g: flags["GAME"] = not flags["GAME"] elif event.key == K_t and game.tournament_is_running(): tournamentinfo = not tournamentinfo elif event.key == K_z: zoomout = not zoomout elif event.key == K_m: mouselock = not mouselock elif event.key == K_PAGEUP and len(shipids) > 0: if trackshipid == None: trackshipid = shipids[0] else: trackshipid = shipids[shipids.index(trackshipid) - 1] elif event.key == K_PAGEDOWN and len(shipids) > 0: if trackshipid == None: trackshipid = shipids[-1] else: trackshipid = shipids[(shipids.index(trackshipid) + 1) % len(shipids)] elif event.key == K_END: trackshipid = None mlh.filter = None elif event.key == K_y: dynamiccamera = not dynamiccamera if not dynamiccamera: trackshipid = None elif event.key == K_l: logintercept = not logintercept logger = logging.getLogger() if logintercept: logger.addHandler(mlh) else: logger.removeHandler(mlh) mlh.messages.clear() elif event.key == K_a: if mousemode not in ("AddAsteroid", "AddPlanet", "AddBlackHole", "AddNebula", "AddBubble"): mousemode = "AddAsteroid" elif mousemode == "AddAsteroid": mousemode = "AddPlanet" elif mousemode == "AddPlanet": mousemode = "AddBlackHole" elif mousemode == "AddBlackHole": mousemode = "AddNebula" elif mousemode == "AddNebula" and isinstance(game, KingOfTheBubbleGame): mousemode = "AddBubble" else: mousemode = None elif event.key == K_k: if mousemode == "Destroy": mousemode = None else: mousemode = "Destroy" elif event.key == K_e: if mousemode == "Explode": mousemode = None else: mousemode = "Explode" elif event.key == K_v: if mousemode == "Move": mousemode = None zoomout = prevzoom else: mousemode = "Move" prevzoom = zoomout zoomout = True elif event.key == K_r: showroundtime = not showroundtime elif event.type == MOUSEBUTTONDOWN: if zoomout: x = event.pos[0]*scalefactorx y = event.pos[1]*scalefactory else: x, y = event.pos[0]-offsetx, event.pos[1]-offsety #print zoomout, x, y if mousemode == "Move" and trackshipid != None: mousemode = None zoomout = prevzoom objects[trackshipid]._worldobj.body.position = (x, y) elif mousemode == "Destroy": mousemode = None v = Vec2d(x, y) for lst in objects, bgobjects: for obj in lst: if math.sqrt(obj._worldobj.body.position.get_dist_sqrd(v)) <= 32: logging.info("[GUI] Destroying Object #%d", obj._worldobj.id) if isinstance(obj, ShipGUI): obj._worldobj.killed = True world.remove(obj._worldobj) break elif mousemode == "Explode": mousemode = None world.causeExplosion((x, y), 256, 1000) elif mousemode == "AddAsteroid": mousemode = None world.append(Asteroid((x, y))) elif mousemode == "AddPlanet": mousemode = None world.append(Planet((x, y))) elif mousemode == "AddBlackHole": mousemode = None world.append(BlackHole((x, y))) elif mousemode == "AddNebula": mousemode = None world.append(Nebula((x, y), (512, 128))) elif mousemode == "AddBubble": mousemode = None game.addBubbles(world, 1, pos=(x, y)) elif zoomout and event.button == 1: # zoom in zoomout = False #xoffset = -(((event.pos[0] - resolution[0]/2) % 16)*16 * scalefactorx) #yoffset = -(((event.pos[1] - resolution[1]/2) % 16)*16 * scalefactory) offsetx, offsety = centerViewOnWorld((event.pos[0]*scalefactorx, event.pos[1]*scalefactory)) else: # recenter on new click offsetx, offsety = centerViewOnWorld((event.pos[0]*scalefactorx, event.pos[1]*scalefactory)) #endregion # Tracks a ship on screen if len(shipids) == 0 or trackshipid not in objects: trackshipid = None if dynamiccamera: trackshipid = game.game_get_current_leader_list()[0].id if trackshipid != None: mlh.filter = "#" + repr(trackshipid) offsetx, offsety = centerViewOnWorld(objects[trackshipid]._worldobj.body.position) # Pans world with mouse if not mouselock: # TODO Edge Detect pos = pygame.mouse.get_pos() if pos[0] < MOUSELOCK_RANGE: offsetx += 48 elif pos[0] > resolution[0] - MOUSELOCK_RANGE: offsetx -= 48 if pos[1] < MOUSELOCK_RANGE: offsety += 48 elif pos[1] > resolution[1] - MOUSELOCK_RANGE: offsety -= 48 #eif if offsetx > 0: offsetx = 0 if offsety > 0: offsety = 0 if offsetx < maxoffsetx: offsetx = maxoffsetx if offsety < maxoffsety: offsety = maxoffsety if zoomout: pygame.transform.scale(worldsurface, resolution, windowSurface) else: windowSurface.blit(worldsurface, (offsetx, offsety)) if flags["DEBUG"] or showip: ip = bigfont.render(ipaddress, False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,0)) if not game.round_get_has_started(): ip = bigfont.render("Waiting For Start - Press Space", False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,resolution[1]/2-ip.get_height()/2)) if game.laststats != None: x = 0 for i in game.laststats: windowSurface.blit(font.render(i, False, (192, 192, 255)), (resolution[0]/2-300, resolution[1]/2 + 64 + 12*x)) x += 1 if showplayerlist > 0: x = 0 for i in game.gui_get_player_stats(all=(showplayerlist == 1)): windowSurface.blit(font.render(i, False, (192, 192, 192)), (resolution[0]-300, 64 + 12*x)) x += 1 if showplayerlist == 1: windowSurface.blit(font.render(repr(x) + " Players Connected", False, (192, 192, 192)), (resolution[0]-300, 64 + 12 * x)) else: windowSurface.blit(font.render(repr(x) + " Players In Round", False, (192, 192, 192)), (resolution[0]-300, 64 + 12 * x)) if showroundtime: ip = bigfont.render(str(datetime.timedelta(seconds=game.round_get_time_remaining())), False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2,32)) if flags["DEBUG"]: mpos = pygame.mouse.get_pos() if zoomout: # TODO: Need to refactor and centralize these calculations as part of 'Camera' system x = mpos[0]*scalefactorx y = mpos[1]*scalefactory else: x, y = mpos[0]-offsetx, mpos[1]-offsety windowSurface.blit(font.render("DEBUG MODE", False, (255, 255, 255)), (resolution[0]/2-38,32)) windowSurface.blit(font.render("FPS: " + repr(fpsClock.get_fps()), False, (255, 255, 255)), (0,0)) windowSurface.blit(font.render("T: " + repr(len(threading.enumerate())), False, (192, 192, 192)), (0, 12)) windowSurface.blit(font.render("World Offset: " + repr((-offsetx, -offsety)), False, (255, 255, 255)), (0,24)) windowSurface.blit(font.render("World Size: " + repr(world.size) + " - " + repr((int(x), int(y))), False, (255, 255, 255)), (0,36)) windowSurface.blit(font.render("World Objects: " + repr(len(world)), False, (255, 255, 255)), (0,48)) if mouselock: windowSurface.blit(font.render("MOUSELOCK: ON", False, (255, 255, 255)), (resolution[0]-104,0)) else: windowSurface.blit(font.render("MOUSELOCK: OFF", False, (255, 255, 255)), (resolution[0]-104,0)) windowSurface.blit(font.render(repr(flags), False, (255, 255, 255)), (0, resolution[1]-12)) if logintercept: for i in xrange(len(mlh.messages)): if i >= len(mlh.messages): break c = 145 + i*2 windowSurface.blit(font.render(mlh.messages[i], False, (c, c, c)), (10, 64 + 12*i)) if trackshipid == None: windowSurface.blit(font.render("Tracking Off", False, (255, 255, 255)), (resolution[0]/2-36,resolution[1]-12)) if flags["GAME"]: game.gui_draw_game_screen_info(windowSurface, flags) if tournamentinfo: game.gui_draw_tournament_bracket(windowSurface, flags) if trackshipid != None: n = font.render("Tracking: " + objects[trackshipid]._worldobj.player.name, False, objects[trackshipid]._worldobj.player.color) windowSurface.blit(n, (resolution[0]/2-n.get_width()/2,resolution[1]-12)) if mousemode == "Destroy": ip = bigfont.render("DESTROY OBJECT BY CLICK", False, (255, 0, 0)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode == "Explode": ip = bigfont.render("CLICK TO CAUSE EXPLOSION FORCE", False, (255, 0, 0)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode != None and mousemode[:3] == "Add": ip = bigfont.render("Click to Add " + mousemode[3:], False, (255, 255, 0)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) if mousemode == "Move" and trackshipid == None: ip = bigfont.render("TRACK OBJECT BEFORE MOVING", False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) elif mousemode == "Move": ip = bigfont.render("CLICK TO MOVE "+objects[trackshipid]._worldobj.player.name, False, (255, 255, 255)) windowSurface.blit(ip, (resolution[0]/2-ip.get_width()/2, resolution[1]/2-ip.get_height()/2)) pygame.display.update() fpsClock.tick(30) # keep in sync with physics engine? #TODO: Add Logging??? # close out a game game.end() logging.info("Closing Pygame...") print "Closing Pygame" pygame.quit()
class BaubleHuntGame(BasicGame): def __init__(self, cfgobj): bb = cfgobj.getfloat("BaubleHunt", "bauble_percent_blue") yb = cfgobj.getfloat("BaubleHunt", "bauble_percent_yellow") rb = cfgobj.getfloat("BaubleHunt", "bauble_percent_red") self._mv = cfgobj.getint("BaubleHunt", "bauble_points_red") self.__valuetable = [(bb, cfgobj.getint("BaubleHunt", "bauble_points_blue")), (bb+yb, cfgobj.getint("BaubleHunt", "bauble_points_yellow")), (bb+yb+rb, self._mv)] self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self).__init__(cfgobj) self.__maxcarry = self.cfg.getint("BaubleHunt", "ship_cargo_size") def world_create(self, pys=True): w = ConfiguredWorld(self, self.cfg, pys) # Add some initial small Baubles self.__addBaubles(w, self.cfg.getint("BaubleHunt", "bauble_initial")) return w def game_get_info(self): return {"GAMENAME": "BaubleHunt"} def _world_reset(self): self.__bases = ThreadSafeDict() self.__baubles = ThreadSafeDict() super(BaubleHuntGame, self)._world_reset() def player_added(self, player, reason): super(BaubleHuntGame, self).player_added(player, reason) player.carrying = [] if reason == BasicGame._ADD_REASON_REGISTER_: player.totalcollected = 0 elif reason == BasicGame._ADD_REASON_START_: player.totalcollected = 0 self.__addHomeBase(player) self.__addBaubles(self.world, self.cfg.getint("BaubleHunt", "bauble_per_player")) elif self.__bases.has_key(player.netid): self.__bases[player.netid].newOwner(player.object) def __addHomeBase(self, player, force=False): logging.info("Add HomeBase (%s) for Player %d", repr(force), player.netid) # add player bauble b = HomeBase(self.getHomeBasePosition(), player.object) self.__bases[player.netid] = b self.world.append(b) logging.info("Done Adding HomeBase") # Ignores Baubles def getHomeBasePosition(self): pos = (0,0) x = 0 n = 1 while n > 0 and x < 15: x += 1 pos = (random.randint(100, self.world.width - 100), random.randint(100, self.world.height - 100)) objs = self.world.getObjectsInArea(pos, 200) for i in xrange(len(objs)-1, -1, -1): if isinstance(objs[i], Bauble): del objs[i] n = len(objs) return pos def __addBaubles(self, w, num, force=False): logging.info("Adding %d Baubles (%s)", num, repr(force)) for i in xrange(num): r = random.random() v = 0 for ent in self.__valuetable: if r < ent[0]: v = ent[1] break b = Bauble(getPositionAwayFromOtherObjects(w, 80, 30, force), v) if v == self._mv: self.__baubles[b.id] = b w.append(b) logging.info("Done Adding Baubles") def world_physics_pre_collision(self, shapes): types = [] objs = [] for shape in shapes: objs.append(self.world[shape.id]) types.append(friendly_type(objs[-1])) if "Ship" in types and "HomeBase" in types: ship = None myhome = None homes = [] for obj in objs: if isinstance(obj, HomeBase): homes.append(obj) elif isinstance(obj, Ship): ship = obj if ship != None: logging.info("Ship #%d hit bases %d owner id #%d", ship.id, len(homes), homes[0].owner.id) for h in homes: if ship.id == h.owner.id: myhome = h logging.info("Ship #%d hit their base", ship.id) break #eif #next else: logging.error("Ship not found after collision with Ship?") #eif if myhome != None: return [ False, [ [self.depositBaubles, ship, myhome] ] ] else: logging.info("Ship #%d hit some other base", ship.id) return [ False, [] ] if "Ship" in types and "Bauble" in types: b = [] ship = None for obj in objs: if isinstance(obj, Bauble): b.append(obj) elif isinstance(obj, Ship): ship = obj #eif #next return [ False, [ [self.collectBaubles, ship, b] ] ] elif "HomeBase" in types or "Bauble" in types: return [ False, [] ] return super(BaubleHuntGame, self).world_physics_pre_collision(shapes) def collectBaubles(self, ship, para): logging.info("Collected Baubles Ship #%d", ship.id) sound = True for bauble in para[0]: if len(ship.player.carrying) < self.__maxcarry: ship.player.carrying.append(bauble) if sound: sound = False ship.player.sound = "BAUBLE" if self.__baubles.has_key(bauble.id): del self.__baubles[bauble.id] self.world.remove(bauble) bauble.destroyed = True self.__addBaubles(self.world, 1, True) #eif logging.info("Done Collecting Baubles #%d", ship.id) def depositBaubles(self, ship, para): home = para[0] logging.info("Player Depositing Baubles #%d", ship.id) for b in ship.player.carrying: self.player_update_score(ship.player, b.value) home.stored += b.value ship.player.totalcollected += len(ship.player.carrying) if len(ship.player.carrying) > 0: ship.player.sound = "COLLECT" ship.player.carrying = [] def player_died(self, player, gone): # if ship destroyed, put baubles stored back for b in player.carrying: b.body.position = (player.object.body.position[0] + random.randint(-10, 10), player.object.body.position[1] + random.randint(-10, 10)) if b.value == self._mv: self.__baubles[b.id] = b self.world.append(b) self.world.causeExplosion(player.object.body.position, 32, 1000) # Remove player's base if they're gone if gone and self.__bases.has_key(player.netid): self.world.remove(self.__bases[player.netid]) del self.__bases[player.netid] return super(BaubleHuntGame, self).player_died(player, gone) def game_get_extra_environment(self, player): if player.netid in self.__bases: # Check if Player still around? v = 0 for b in player.carrying: v += b.value baubles = [] for b in self.__baubles: baubles.append(intpos(b.body.position)) env = super(BaubleHuntGame, self).game_get_extra_environment(player) env.update({"POSITION": intpos(self.__bases[player.netid].body.position), "BAUBLES": baubles, "STORED": len(player.carrying), "STOREDVALUE": v, "COLLECTED": player.totalcollected}) return env else: return {} def game_get_extra_radar_info(self, obj, objdata): """ Called by the World when the obj is being radared """ super(BaubleHuntGame, self).game_get_extra_radar_info(obj, objdata) if hasattr(obj, "player"): objdata["NUMSTORED"] = len(obj.player.carrying) def player_get_stat_string(self, player): return str(int(player.score)) + " in " + str(player.totalcollected) + " : " + player.name + " c.v. " + str(sum(b.value for b in player.carrying)) + " in " + str(len(player.carrying)) def round_over(self): logging.info("Round Over") self.__btimer.cancel() # prevent more baubles from being spawned! super(BaubleHuntGame, self).round_over() def gui_draw_game_world_info(self, surface, flags): for player in self.game_get_current_player_list(): if player.object != None: # draw number of objects carried text = debugfont().render(repr(len(player.carrying)), False, player.color) surface.blit(text, (player.object.body.position[0]+30, player.object.body.position[1]-4)) # draw line between player and HomeBase if flags["DEBUG"] and self.__bases.has_key(player.netid): pygame.draw.line(surface, player.color, intpos(player.object.body.position), intpos(self.__bases[player.netid].body.position)) # draw number of baubles carried by player def round_start(self): logging.info("Game Start") super(BaubleHuntGame, self).round_start() # start Bauble Spawn Timer self.newBaubleTimer() def newBaubleTimer(self): ntime = self.cfg.getint("BaubleHunt", "bauble_timer") if ntime > 0: self.__btimer = RoundTimer(ntime, self.spawnBauble) self.__btimer.start() def spawnBauble(self): logging.info("Spawn Bauble Timer [%d]", thread.get_ident()) self.__addBaubles(self.world, self.cfg.getint("BaubleHunt", "bauble_timer_spawns")) self.newBaubleTimer()