def _find_empty_row(session: GameSession) -> int: possible_rows = set(range(session.num_rows)) for member in GameSessionMembership.non_observer_members(session): possible_rows.remove(member.row) for empty_row in sorted(possible_rows): return empty_row raise InvalidAction("Session is full")
def game_session_request_pickups(sio: ServerApp, session_id: int): current_user = sio.get_current_user() your_membership = GameSessionMembership.get_by_ids(current_user.id, session_id) session: GameSession = your_membership.session if session.state == GameSessionState.SETUP: logger().info(f"Session {session_id}, Row {your_membership.row} " f"requested pickups, but session is setup.") return None if your_membership.is_observer: logger().info( f"Session {session_id}, {current_user.name} requested pickups, but is an observer." ) return None description = session.layout_description row_to_member_name = { member.row: member.effective_name for member in GameSessionMembership.non_observer_members(session) } resource_database = _get_resource_database(description, your_membership.row) result = [] actions: List[GameSessionTeamAction] = list( _query_for_actions(your_membership)) for action in actions: pickup_target = _get_pickup_target(description, action.provider_row, action.provider_location_index) if pickup_target is None: logging.error( f"Action {action} has a location index with nothing.") result.append(None) else: name = row_to_member_name.get(action.provider_row, f"Player {action.provider_row + 1}") result.append({ "provider_name": name, "pickup": _base64_encode_pickup(pickup_target.pickup, resource_database), }) logger().info( f"Session {session_id}, Row {your_membership.row} " f"requested pickups, returning {len(result)} elements for {resource_database.game_enum.value}." ) return { "game": resource_database.game_enum.value, "pickups": result, }
def _find_empty_row(session: GameSession) -> int: empty_row = 0 for possible_slot in GameSessionMembership.non_observer_members(session): if empty_row != possible_slot.row: break else: empty_row += 1 if empty_row >= session.num_rows: raise InvalidAction("Session is full") return empty_row
def _emit_game_session_pickups_update(sio: ServerApp, membership: GameSessionMembership): session: GameSession = membership.session if session.state == GameSessionState.SETUP: raise RuntimeError("Unable to emit pickups during SETUP") if membership.is_observer: raise RuntimeError("Unable to emit pickups for observers") description = session.layout_description row_to_member_name = { member.row: member.effective_name for member in GameSessionMembership.non_observer_members(session) } resource_database = _get_resource_database(description, membership.row) result = [] actions: List[GameSessionTeamAction] = list(_query_for_actions(membership)) for action in actions: pickup_target = _get_pickup_target(description, action.provider_row, action.provider_location_index) if pickup_target is None: logging.error( f"Action {action} has a location index with nothing.") result.append(None) else: name = row_to_member_name.get(action.provider_row, f"Player {action.provider_row + 1}") result.append({ "provider_name": name, "pickup": _base64_encode_pickup(pickup_target.pickup, resource_database), }) logger().info( f"{_describe_session(session, membership)} " f"notifying {resource_database.game_enum.value} of {len(result)} pickups." ) data = { "game": resource_database.game_enum.value, "pickups": result, } flask_socketio.emit("game_session_pickups_update", data, room=f"game-session-{session.id}-{membership.user.id}")
def game_session_admin_player(sio: ServerApp, session_id: int, user_id: int, action: str, arg): _verify_has_admin(sio, session_id, user_id) action: SessionAdminUserAction = SessionAdminUserAction(action) session: GameSession = database.GameSession.get_by_id(session_id) membership = GameSessionMembership.get_by_ids(user_id, session_id) if action == SessionAdminUserAction.KICK: _add_audit_entry( sio, session, f"Kicked {membership.effective_name}" if membership.user != sio.get_current_user() else "Left session") membership.delete_instance() if not list(session.players): session.delete_instance(recursive=True) logger().info( f"{_describe_session(session)}. Kicking user {user_id} and deleting session." ) else: logger().info( f"{_describe_session(session)}. Kicking user {user_id}.") elif action == SessionAdminUserAction.MOVE: offset: int = arg if membership.is_observer is None: raise InvalidAction("Player is an observer") new_row = membership.row + offset if new_row < 0: raise InvalidAction("New position is negative") if new_row >= session.num_rows: raise InvalidAction("New position is beyond num of rows") team_members = [None] * session.num_rows for member in GameSessionMembership.non_observer_members(session): team_members[member.row] = member while (0 <= new_row < session.num_rows) and team_members[new_row] is not None: new_row += offset if new_row < 0 or new_row >= session.num_rows: raise InvalidAction("No empty slots found in this direction") with database.db.atomic(): logger().info( f"{_describe_session(session)}, User {user_id}. " f"Performing {action}, new row is {new_row}, from {membership.row}." ) membership.row = new_row membership.save() elif action == SessionAdminUserAction.SWITCH_IS_OBSERVER: if membership.is_observer: membership.row = _find_empty_row(session) else: membership.row = None logger().info( f"{_describe_session(session)}, User {user_id}. Performing {action}, " f"new row is {membership.row}.") membership.save() elif action == SessionAdminUserAction.SWITCH_ADMIN: # Must be admin for this _verify_has_admin(sio, session_id, None, allow_when_no_admins=True) num_admins = GameSessionMembership.select().where( GameSessionMembership.session == session_id, GameSessionMembership.admin == True).count() if membership.admin and num_admins <= 1: raise InvalidAction("can't demote the only admin") membership.admin = not membership.admin _add_audit_entry( sio, session, f"Made {membership.effective_name} {'' if membership.admin else 'not '}an admin" ) logger().info( f"{_describe_session(session)}, User {user_id}. Performing {action}, " f"new status is {membership.admin}.") membership.save() elif action == SessionAdminUserAction.CREATE_PATCHER_FILE: player_names = {i: f"Player {i + 1}" for i in range(session.num_rows)} for member in GameSessionMembership.non_observer_members(session): player_names[member.row] = member.effective_name layout_description = session.layout_description players_config = PlayersConfiguration( player_index=membership.row, player_names=player_names, ) preset = layout_description.get_preset(players_config.player_index) cosmetic_patches = preset.game.data.layout.cosmetic_patches.from_json( arg) _add_audit_entry(sio, session, f"Made an ISO for row {membership.row + 1}") data_factory = preset.game.patch_data_factory(layout_description, players_config, cosmetic_patches) try: return data_factory.create_data() except Exception as e: logger().exception("Error when creating patch data") raise InvalidAction(f"Unable to export game: {e}") elif action == SessionAdminUserAction.ABANDON: # FIXME raise InvalidAction("Abandon is NYI") _emit_session_meta_update(session)
def game_session_admin_player(sio: ServerApp, session_id: int, user_id: int, action: str, arg): _verify_has_admin(sio, session_id, user_id) action: SessionAdminUserAction = SessionAdminUserAction(action) session: GameSession = database.GameSession.get_by_id(session_id) membership = GameSessionMembership.get_by_ids(user_id, session_id) if action == SessionAdminUserAction.KICK: membership.delete_instance() if not list(session.players): session.delete_instance(recursive=True) logger().info( f"Session {session_id}. Kicking user {user_id} and deleting session." ) else: logger().info(f"Session {session_id}. Kicking user {user_id}.") elif action == SessionAdminUserAction.MOVE: offset: int = arg if membership.is_observer is None: raise InvalidAction("Player is an observer") new_row = membership.row + offset if new_row < 0: raise InvalidAction("New position is negative") if new_row >= session.num_rows: raise InvalidAction("New position is beyond num of rows") team_members = [None] * session.num_rows for member in GameSessionMembership.non_observer_members(session): team_members[member.row] = member while (0 <= new_row < session.num_rows) and team_members[new_row] is not None: new_row += offset if new_row < 0 or new_row >= session.num_rows: raise InvalidAction("No empty slots found in this direction") with database.db.atomic(): logger().info( f"Session {session_id}, User {user_id}. " f"Performing {action}, new row is {new_row}, from {membership.row}." ) membership.row = new_row membership.save() elif action == SessionAdminUserAction.SWITCH_IS_OBSERVER: if membership.is_observer: membership.row = _find_empty_row(session) else: membership.row = None logger().info( f"Session {session_id}, User {user_id}. Performing {action}, new row is {membership.row}." ) membership.save() elif action == SessionAdminUserAction.SWITCH_ADMIN: # Must be admin for this _verify_has_admin(sio, session_id, None, allow_when_no_admins=True) num_admins = GameSessionMembership.select().where( GameSessionMembership.session == session_id, GameSessionMembership.admin == True).count() if membership.admin and num_admins <= 1: raise InvalidAction("can't demote the only admin") membership.admin = not membership.admin logger().info( f"Session {session_id}, User {user_id}. Performing {action}, new status is {membership.admin}." ) membership.save() elif action == SessionAdminUserAction.CREATE_PATCHER_FILE: cosmetic_patches = CosmeticPatches.from_json_dict(arg) player_names = {i: f"Player {i + 1}" for i in range(session.num_rows)} for member in GameSessionMembership.non_observer_members(session): player_names[member.row] = member.effective_name players_config = PlayersConfiguration( player_index=membership.row, player_names=player_names, ) return patcher_file.create_patcher_file(session.layout_description, players_config, cosmetic_patches) elif action == SessionAdminUserAction.ABANDON: # FIXME raise InvalidAction("Abandon is NYI") _emit_session_update(session)