class SpydGetPlayerInfoMessageHandler(object): msgtype = 'get_room_info' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): room_name = message['room'] room = spyd_server.room_manager.get_room(name=room_name, fuzzy=False) if room is None: raise Exception("Unknown room.") room_info = { 'is_paused': room.is_paused, 'is_resuming': room.is_resuming, 'timeleft': room.timeleft, 'is_intermission': room.is_intermission, 'resume_delay': room.resume_delay, 'mode': room.mode_name, 'map': room.map_name, 'players': [player.uuid for player in room.players], 'show_awards': room.show_awards, 'mastermode': room.mastermode, 'mastermask': room.mastermask, 'temporary': room.temporary, 'maxplayers': room.maxplayers } gep_client.send({'msgtype': 'room_info', 'room': room.name, 'room_info': room_info}, message.get('reqid'))
class ResumeDelayCommand(CommandBase): name = "resumedelay" functionality = Functionality( "spyd.game.commands.resumedelay.execute", "You do not have permission to execute {action#command}", command=name) usage = "(seconds)" description = "View or set the amount of time to count down before starting or resuming a game." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments): try: room.resume_delay = max(0, min(10, int(arguments[0]))) room._broadcaster.server_message( info( "{name#client} has set the resume delay to {value#resume_delay} seconds.", client=client, resume_delay=room.resume_delay)) except: raise UsageError("The resume delay must be an number.") else: client.send_server_message( info("The resume delay is {value#resume_delay} seconds.", resume_delay=room.resume_delay))
class FollowCommand(CommandBase): name = "follow" functionality = Functionality( "spyd.game.commands.room.execute", "You do not have permission to execute {action#command}", command=name) usage = "" description = "Follow the last player to leave this room." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): room_name = room.last_destination_room if room_name is None: raise GenericError( "No players have left this room for another recently. Perhaps join another existing room with {action#room}.", room='room') target_room = room.manager.get_room(room_name, True) if target_room is None: raise GenericError( "Could not join {value#room_name}. Room no longer exists. Perhaps create it with {action#room_create}", room_name=room_name, room_create='room_create') room.manager.client_change_room(client, target_room)
class SpydSetRoomPausedMessageHandler(object): msgtype = 'set_room_paused' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): room_name = message['room'] pause = message['pause'] room_message = message['message'] target_room = spyd_server.room_manager.get_room(name=room_name, fuzzy=False) if target_room is None: raise Exception("Unknown room.") try: if pause: target_room.pause() else: target_room.resume() if room_message: target_room.server_message(str(room_message)) except: traceback.print_exc() gep_client.send({ "msgtype": "status", "status": "success" }, message.get('reqid'))
class GroupsCommand(CommandBase): name = "groups" functionality = Functionality( "spyd.game.commands.groups.execute", "You do not have permission to execute {action#command}", command=name) usage = "(cn)" description = "Displays the groups of the indicated player or yourself." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments): target = room.get_client(int(arguments[0])) else: target = client formatted_groups = ", ".join([ smf.format("{value#group}", group=group) for group in target._client_permissions.get_group_names() ]) client.send_server_message( info("groups: {formatted_groups}", formatted_groups=formatted_groups))
class RoomCommand(CommandBase): name = "room" functionality = Functionality( "spyd.game.commands.room.execute", "You do not have permission to execute {action#command}", command=name) usage = "<room name>" description = "Join a specified room." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments) < 1: raise GenericError("Please specify a room name.") room_name = arguments[0] target_room = room.manager.get_room(room_name, True) if target_room is None: raise GenericError( "Could not resolve {value#room_name} to a room. Perhaps create it with {action#room_create}", room_name=room_name, room_create='room_create') room.manager.client_change_room(client, target_room)
class SpydSetPlayerRoomMessageHandler(object): msgtype = 'set_player_room' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): room_name = message['room'] player_uuid = message['player'] player_message = message['message'] player = Player.instances_by_uuid.get(player_uuid, None) if player is None: raise Exception("Unknown player.") client = player.client target_room = spyd_server.room_manager.get_room(name=room_name, fuzzy=False) if target_room is None: room_factory = spyd_server.room_manager.room_factory target_room = room_factory.build_room(room_name, 'temporary') target_room.temporary = True spyd_server.room_manager.client_change_room(client, target_room, False) if player_message: client.send_server_message(str(player_message)) gep_client.send({"msgtype": "status", "status": "success"}, message.get('reqid'))
def test_master_overrides_player_denies_noobishness(self): be_a_noob_functionality = Functionality("server.be_n00b") self.assertTrue( self.permission_resolver.groups_allow(["local.player"], be_a_noob_functionality)) self.assertFalse( self.permission_resolver.groups_allow( ["local.player", "local.master"], be_a_noob_functionality))
class SpydGetPlayerInfoMessageHandler(object): msgtype = 'get_player_info' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): player_uuid = message['player'] player = Player.instances_by_uuid.get(player_uuid, None) if player is None: raise Exception("Unknown player.") state = player.state player_game_state = { 'is_spectator': state.is_spectator, 'is_alive': state.is_alive, 'has_quad': state.has_quad, 'frags': state.frags, 'deaths': state.deaths, 'suicides': state.suicides, 'teamkills': state.teamkills, 'damage_dealt': state.damage_dealt, 'damage_spent': state.damage_spent, 'flags': state.flags, 'flag_returns': state.flag_returns, 'health': state.health, 'maxhealth': state.maxhealth, 'armour': state.armour, 'armourtype': state.armourtype, 'gunselect': state.gunselect, 'ammo': state.ammo } player_info = { 'cn': player.cn, 'name': player.name, 'team': player.team_name, 'room': player.room.name, 'host': player.client.host, 'model': player.playermodel, 'isai': player.isai, 'groups': tuple(player.client.get_group_names()), 'game_state': player_game_state } gep_client.send( { 'msgtype': 'player_info', 'player': player.uuid, 'player_info': player_info }, message.get('reqid'))
class SpydGetServerInfoMessageHandler(object): msgtype = 'get_server_info' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): room_manager = spyd_server.room_manager server_info = { "rooms": list(room_manager.rooms.keys()) } gep_client.send({'msgtype': 'server_info', 'server_info': server_info}, message.get('reqid'))
class PauseCommand(CommandBase): name = "pause" functionality = Functionality( "spyd.game.commands.pause.execute", "You do not have permission to execute {action#command}", command=name) usage = "" description = "Pause the game." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): room.handle_client_event('pause_game', client, 1)
class InfoCommand(CommandBase): name = "info" functionality = Functionality( "spyd.game.commands.info.execute", "You do not have permission to execute {action#command}", command=name) usage = "" description = "Displays the server info message." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): client.send_server_message(info(spyd_server.server_info_model.value))
class SpydPingMessageHandler(object): msgtype = 'ping' execute = Functionality(msgtype) @classmethod def handle_message(cls, spyd_server, gep_client, message): server_time = int(time.time() * 1000000) client_time = message['time'] gep_client.send( { 'msgtype': 'pong', 'client_time': client_time, 'server_time': server_time }, message.get('reqid'))
class RoomsCommand(CommandBase): name = "rooms" functionality = Functionality( "spyd.game.commands.rooms.execute", "You do not have permission to execute {action#command}", command=name) usage = "<room name>" description = "Displays the rooms on the server, their player counts, modes, and maps." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): for room in room.manager.rooms.values(): if room.empty: continue client.send_server_message(info(room_info_msg, room=room))
class AuthCommand(CommandBase): name = "auth" description = "Authenticate as admin" functionality = Functionality( "spyd.game.commands.authpass.execute", "You do not have permission to execute {action#command}", command=name) @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if not arguments: raise UsageError("You must provide a password") room.handle_client_event('auth_pass', client, arguments)
class StatsCommand(CommandBase): name = "stats" functionality = Functionality("spyd.game.commands.stats.execute", "You do not have permission to execute {action#command}", command=name) usage = "(cn)" description = "Displays the stats of the indicated player or yourself." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments): player = room.get_player(int(arguments[0])) else: player = client.get_player() time_online_str = shortDurationString(player.client.time_online) client.send_server_message(info(stats_msg, player=player, time_online_str=time_online_str))
class TimeleftCommand(CommandBase): name = "timeleft" functionality = Functionality( "spyd.game.commands.timeleft.execute", "You do not have permission to execute {action#command}", command=name) usage = "(time string)" description = "View or set the amount of time left in the match. Valid time strings could be; +2m -30s 10m 2y" @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments): if room.temporary: if not client.allowed(set_temporary_room_timeleft): raise InsufficientPermissions( set_temporary_room_timeleft.denied_message) else: if not client.allowed(set_permanent_room_timeleft): raise InsufficientPermissions( set_permanent_room_timeleft.denied_message) try: modifier, value = timestring.parseTimeString(raw_args) if modifier == '+': new_timeleft = min(MAXTIMELEFT, max(0, room.timeleft + value)) elif modifier == '-': new_timeleft = min(MAXTIMELEFT, max(0, room.timeleft - value)) elif modifier == '=': new_timeleft = min(MAXTIMELEFT, max(0, value)) timeleft = prettytime.createDurationString(new_timeleft) room._broadcaster.server_message( info(timeleft_set_str, client=client, timeleft=timeleft)) room.timeleft = new_timeleft except timestring.MalformedTimeString: raise GenericError("Invalid time string specified.") else: timeleft = prettytime.createDurationString(room.timeleft) client.send_server_message( info(timeleft_get_str, timeleft=timeleft))
class CommandsCommand(CommandBase): name = "commands" functionality = Functionality( "spyd.game.commands.room.execute", "You do not have permission to execute {action#command}", command=name) usage = "" description = "Displays the list of commands you are permitted to execute." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): available_commands = room.command_executer.get_available_commands( client) formatted_command_list = list(map(format_cmd, available_commands)) client.send_server_message("\f7Commands: " + " | ".join(formatted_command_list))
class ChangeMapCommand(CommandBase): name = "<mode>" functionality = Functionality("spyd.game.commands.resume.execute", "You do not have permission to execute {action#command}", command=name) usage = "<map>" description = "Change the mode and map." @classmethod def handles(cls, room, client, command_string): return command_string in gamemodes @classmethod @defer.inlineCallbacks def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if not client.allowed(set_map_mode_functionality): raise InsufficientPermissions(set_map_mode_functionality.denied_message) mode_name = command_string map_name = arguments[0] map_name = yield resolve_map_name(room, map_name) room.change_map_mode(map_name, mode_name)
class GiveMasterCommand(CommandBase): name = "givemaster" description = "Give master to a client" functionality = Functionality( "spyd.game.commands.givemaster.execute", "You do not have permission to execute {action#command}", command=name) @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): player_name = arguments[0] if arguments else '' players = dict( map(lambda player: (player.name, player.client), room.players)) if not player_name: raise UsageError("You must provide a player") if player_name not in players.keys(): raise GenericError('The player doesn\'t exist') room.handle_client_event('give_master', client, players[player_name])
class RoomCreateCommand(CommandBase): name = "room_create" functionality = Functionality( "spyd.game.commands.room_create.execute", "You do not have permission to execute {action#command}", command=name) usage = "<room name>" description = "Create a room." @classmethod def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): if len(arguments) < 1: raise GenericError("Please specify a room name.") room_name = filtertext(arguments[0], True, MAXROOMLEN) target_room = room.manager.get_room(room_name, False) if target_room is not None: raise GenericError( "Room {room#room} already exists, use {action#command} to enter it.", room=target_room.name, command="room") if duel_room_pattern.match(room_name): raise GenericError( "Room {room#room_name} cannot be created for you because room names with the pattern '#x#' are reserved for 1v1 games.", room_name=room_name) room_factory = room.manager.room_factory target_room = room_factory.build_room(room_name, 'temporary') target_room.temporary = True target_room.masters.add(client) room.manager.client_change_room(client, target_room)
class DuelCommand(CommandBase): name = "duel" functionality = Functionality( "spyd.game.commands.duel.execute", "You do not have permission to execute {action#command}", command=name) usage = "(cn) (mode) (map) | (mode) (map)" description = "Indicate you are looking for a duel or challenge a specific player." @classmethod @defer.inlineCallbacks def execute(cls, spyd_server, room, client, command_string, arguments, raw_args): try: cn, mode_name, map_name = parse_arguments(raw_args) if mode_name is not None: valid_mode_names = list(gamemodes.keys()) mode_name_match = match_fuzzy(str(mode_name), valid_mode_names) if mode_name_match is None: raise GenericError( 'Could not resolve mode name {value#mode_name} to valid mode. Please try again.', mode_name=mode_name) mode_name = mode_name_match if map_name is not None: map_name = yield resolve_map_name(room, map_name) duel_command_msg = duel_command.format(client=client) challenge_details = get_challenge_details(mode_name, map_name) if cn is not None: target = room.get_client(int(cn)) if target is client: raise GenericError("You can't duel yourself.") existing_challenge = get_existing_challenge(target, client) if existing_challenge is not None: begin_duel(room, client, target, existing_challenge) else: save_specific_challenge(client, target, mode_name, map_name) target.send_server_message( info(cn_chall_msg, client=client, challenge_details=challenge_details, duel_command=duel_command_msg)) client.send_server_message(info(chall_sent_msg)) else: save_general_challenge(client, mode_name, map_name) room.server_message(info(looking_msg, client=client, challenge_details=challenge_details, duel_command=duel_command_msg), exclude=(client, )) client.send_server_message(info(chall_sent_msg)) except: traceback.print_exc()
def test_functionality(self): f = Functionality('test', 'No access to test.') self.assertEqual(repr(f), "<Functionality: 'test'>")
def test_ban_denies_connect(self): server_connect_functionality = Functionality("server.connect") self.assertFalse( self.permission_resolver.groups_allow( ["local.ban"], server_connect_functionality))
def test_ban_allows_chat(self): server_chat_functionality = Functionality("server.chat") self.assertTrue( self.permission_resolver.groups_allow( ["local.player", "local.ban"], server_chat_functionality))
from twisted.internet import defer from spyd.game.client.exceptions import InsufficientPermissions from spyd.game.gamemode import get_mode_name_from_num from spyd.game.map.resolve_map_name import resolve_map_name from spyd.permissions.functionality import Functionality from spyd.registry_manager import register set_map_mode_functionality = Functionality( "spyd.game.room.set_map_mode", 'Insufficient permissions to force a map/mode change.') @register('room_client_event_handler') class MapVoteHandler(object): event_type = 'map_vote' @staticmethod @defer.inlineCallbacks def handle(room, client, map_name, mode_num): if not client.allowed(set_map_mode_functionality): raise InsufficientPermissions( set_map_mode_functionality.denied_message) mode_name = get_mode_name_from_num(mode_num) map_name = yield resolve_map_name(room, map_name) room.change_map_mode(map_name, mode_name)
def test_master_overrides_ban_allows_connect(self): server_connect_functionality = Functionality("server.connect") self.assertTrue( self.permission_resolver.groups_allow( ["local.player", "local.master", "local.ban"], server_connect_functionality))
def test_master_inherits_from_player(self): server_connect_functionality = Functionality("server.connect") self.assertTrue( self.permission_resolver.groups_allow( ["local.master"], server_connect_functionality))
from spyd.game.client.exceptions import InsufficientPermissions, StateError from spyd.game.server_message_formatter import info from spyd.permissions.functionality import Functionality from spyd.registry_manager import register pause_resume_functionality = Functionality( "spyd.game.room.pause_resume", 'Insufficient permissions to pause or resume the game.') @register('room_client_event_handler') class PauseGameHandler(object): event_type = 'pause_game' @staticmethod def handle(room, client, pause): if not client.allowed(pause_resume_functionality): raise InsufficientPermissions( pause_resume_functionality.denied_message) if pause: if room.is_paused and not room.is_resuming: raise StateError('The game is already paused.') room.pause() room._broadcaster.server_message( info(f"{client.get_player().name} has paused the game.")) elif not pause: if not room.is_paused: raise StateError('The game is already resumed.') room.resume() room._broadcaster.server_message(
from spyd.game.client.client_permissions import ClientPermissions from spyd.game.client.client_player_collection import ClientPlayerCollection from spyd.game.client.exceptions import InsufficientPermissions, StateError, UsageError, GenericError from spyd.game.client.message_handlers import get_message_handlers from spyd.game.client.room_group_provider import RoomGroupProvider from spyd.game.player.player import Player from spyd.game.room.exceptions import RoomEntryFailure from spyd.game.server_message_formatter import error, smf, denied, state_error, usage_error from spyd.permissions.functionality import Functionality from spyd.protocol import swh from spyd.utils.constrain import ConstraintViolation from spyd.utils.filtertext import filtertext from spyd.utils.ping_buffer import PingBuffer bypass_ban = Functionality("spyd.game.client.bypass_ban") logger = logging.getLogger(__name__) class Client(object): ''' Handles the per client networking, and distributes the messages out to the players (main, bots). ''' def __init__(self, protocol, clientnum_handle, room, auth_world_view, permission_resolver, event_subscription_fulfiller, servinfo_domain, punitive_model): self.cn_handle = clientnum_handle self.cn = clientnum_handle.cn self.room = room self.connection_sequence_complete = False self._client_player_collection = ClientPlayerCollection(self.cn)