def game_session_collect_locations(sio: ServerApp, session_id: int, pickup_locations: Tuple[int, ...]): current_user = sio.get_current_user() session: GameSession = database.GameSession.get_by_id(session_id) membership = GameSessionMembership.get_by_ids(current_user.id, session_id) if session.state != GameSessionState.IN_PROGRESS: raise InvalidAction( "Unable to collect locations of sessions that aren't in progress") if membership.is_observer: raise InvalidAction("Observers can't collect locations") description = session.layout_description receiver_players = set() for location in pickup_locations: receiver_player = _collect_location(session, membership, description, location) if receiver_player is not None: receiver_players.add(receiver_player) if not receiver_players: return for receiver_player in receiver_players: try: receiver_membership = GameSessionMembership.get_by_session_position( session, row=receiver_player) flask_socketio.emit( "game_has_update", { "session": session_id, "row": receiver_player, }, room= f"game-session-{receiver_membership.session.id}-{receiver_membership.user.id}" ) except peewee.DoesNotExist: pass _emit_session_update(session)
def _change_row(sio: ServerApp, session: GameSession, arg: Tuple[int, dict]): if len(arg) != 2: raise InvalidAction("Missing arguments.") row_id, preset_json = arg _verify_has_admin(sio, session.id, sio.get_current_user().id) _verify_in_setup(session) _verify_no_layout_description(session) preset = _get_preset(preset_json) if preset.game != RandovaniaGame.PRIME2: raise InvalidAction("Only Prime 2 presets allowed.") try: with database.db.atomic(): preset_row = GameSessionPreset.get( GameSessionPreset.session == session, GameSessionPreset.row == row_id) preset_row.preset = json.dumps(preset.as_json) logger().info(f"Session {session.id}: Changing row {row_id}.") preset_row.save() except peewee.DoesNotExist: raise InvalidAction(f"invalid row: {row_id}")
def game_session_collect_locations(sio: ServerApp, session_id: int, pickup_locations: Tuple[int, ...]): current_user = sio.get_current_user() session: GameSession = database.GameSession.get_by_id(session_id) membership = GameSessionMembership.get_by_ids(current_user.id, session_id) if session.state != GameSessionState.IN_PROGRESS: raise InvalidAction( "Unable to collect locations of sessions that aren't in progress") if membership.is_observer: raise InvalidAction("Observers can't collect locations") logger().info( f"{_describe_session(session, membership)} found items {pickup_locations}" ) description = session.layout_description receiver_players = set() for location in pickup_locations: receiver_player = _collect_location(session, membership, description, location) if receiver_player is not None: receiver_players.add(receiver_player) if not receiver_players: return for receiver_player in receiver_players: try: receiver_membership = GameSessionMembership.get_by_session_position( session, row=receiver_player) _emit_game_session_pickups_update(sio, receiver_membership) except peewee.DoesNotExist: pass _emit_session_actions_update(session)
def _update_layout_generation(sio: ServerApp, session: GameSession, active: bool): _verify_has_admin(sio, session.id, None) _verify_in_setup(session) if active: if session.generation_in_progress is None: session.generation_in_progress = sio.get_current_user() else: raise InvalidAction( f"Generation already in progress by {session.generation_in_progress.name}." ) else: session.generation_in_progress = None session.save()
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 _reset_session(sio: ServerApp, session: GameSession): raise InvalidAction("Restart session is not yet implemented.")
def _verify_no_layout_description(session: GameSession): if session.layout_description_json is not None: raise InvalidAction("Session has a generated game")
def _verify_in_setup(session: GameSession): if session.state != GameSessionState.SETUP: raise InvalidAction("Session is not in setup")
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)
async def test_handle_network_errors_success(skip_qtbot, qapp): callee = AsyncMock() callee.return_value = MagicMock() data = MagicMock() # Run wrapped = qt_network_client.handle_network_errors(callee) result = await wrapped(qapp, "foo", data) # Assert callee.assert_awaited_once_with(qapp, "foo", data) assert result is callee.return_value @pytest.mark.parametrize(["exception", "title", "message"], [ (InvalidAction("something"), "Invalid action", "Invalid Action: something"), (ServerError(), "Server error", "An error occurred on the server while processing your request."), (NotAuthorizedForAction(), "Unauthorized", "You're not authorized to perform that action."), (NotLoggedIn(), "Unauthenticated", "You must be logged in."), (RequestTimeout("5s timeout"), "Connection Error", "<b>Timeout while communicating with the server:</b><br /><br />Request timed out: 5s timeout<br />" "Further attempts will wait for longer."), ]) async def test_handle_network_errors_exception(skip_qtbot, qapp, mocker, exception, title, message): mock_dialog = mocker.patch("randovania.gui.lib.async_dialog.warning", new_callable=AsyncMock) callee = AsyncMock()