processClientEvent function

Future<ServerResponse?> processClientEvent(
  1. WorldEvent? event,
  2. Channel channel,
  3. WorldState state, {
  4. required AssetManager assetManager,
  5. bool allowServerEvents = false,
  6. ChallengeManager? challengeManager,
  7. UserManager? userManager,
})

Implementation

Future<ServerResponse?> processClientEvent(
  WorldEvent? event,
  Channel channel,
  WorldState state, {
  required AssetManager assetManager,
  bool allowServerEvents = false,
  ChallengeManager? challengeManager,
  UserManager? userManager,
}) async {
  buildInitialize() => WorldInitialized(
    table: state.protectTable(channel),
    info: state.info,
    id: channel,
    packsSignature: assetManager
        .createSignature(state.info.packs.toSet())
        .values
        .toList(),
    teamMembers: state.teamMembers,
  );

  if (event == null) {
    if (challengeManager != null) {
      final challenge = challengeManager.generateNewChallenge(channel);
      return UpdateServerResponse.builder(
        AuthenticatedRequested(challenge, isRequired: true),
        channel,
      );
    }
    await userManager?.addUser(channel);
    return UpdateServerResponse.builder(buildInitialize(), channel);
  }
  if (!isValidClientEvent(event, channel, state, assetManager: assetManager)) {
    return null;
  }
  switch (event) {
    case HybridWorldEvent():
      return UpdateServerResponse.builder(
        event,
        kAnyChannel,
        _hybridNeedsUpdate(event, state),
      );
    case LocalWorldEvent():
      return null;
    case ServerWorldEvent():
      return allowServerEvents
          ? UpdateServerResponse.builder(event, kAnyChannel)
          : null;
    case TeamJoinRequest(team: final team):
      return UpdateServerResponse.builder(
        TeamJoined(channel, team),
        kAnyChannel,
        {channel},
      );
    case TeamLeaveRequest(team: final team):
      return UpdateServerResponse.builder(
        TeamLeft(channel, team),
        kAnyChannel,
        {channel},
      );
    case CellRollRequest():
      final table = state.getTableOrDefault(event.cell.table);
      var cell = table.getCell(event.cell.position);
      final random = Random();
      GameObject roll(GameObject object) {
        final figure = assetManager.getFigure(object.asset);
        if (figure == null || !figure.rollable) return object;
        final variations = figure.variations.keys.toList();
        if (variations.isEmpty) return object;
        final picked = variations[random.nextInt(variations.length)];
        return object.copyWith(variation: picked);
      }
      final objectIndex = event.object;
      List<GameObject> objects;
      if (objectIndex != null) {
        final object = cell.objects[objectIndex];
        objects = List<GameObject>.from(cell.objects)
          ..[objectIndex] = roll(object);
      } else {
        objects = cell.objects.map(roll).toList();
      }
      return UpdateServerResponse.builder(
        ObjectsChanged(event.cell, objects),
        kAnyChannel,
      );
    case ShuffleCellRequest():
      final table = state.getTableOrDefault(event.cell.table);
      final cell = table.cells[event.cell.position];
      if (cell == null) return null;
      final positions = List<int>.generate(cell.objects.length, (i) => i)
        ..shuffle();
      return UpdateServerResponse.builder(
        CellShuffled(event.cell, positions),
        kAnyChannel,
      );
    case PacksChangeRequest():
      return UpdateServerResponse.builder(
        WorldInitialized(
          info: state.info.copyWith(
            packs: event.packs.where((e) => assetManager.hasPack(e)).toList(),
          ),
        ),
      );
    case MessageRequest():
      return UpdateServerResponse.builder(
        MessageSent(channel, event.message),
        kAnyChannel,
      );
    case BoardsSpawnRequest():
      final tiles = <VectorDefinition, List<BoardTile>>{};
      for (final (cell, asset) in event.assets.entries.expand(
        (e) => e.value.map((l) => (e.key, l)),
      )) {
        final definition = assetManager.getBoard(asset);
        if (definition == null) return null;
        final size = definition.tiles;
        for (var x = 0; x < size.x; x++) {
          for (var y = 0; y < size.y; y++) {
            final tile = VectorDefinition(x, y);
            final position = cell + tile;
            tiles.putIfAbsent(position, () => []).add(BoardTile(asset, tile));
          }
        }
      }
      return UpdateServerResponse.builder(
        BoardTilesSpawned(event.table, tiles),
        kAnyChannel,
      );
    case BoardRemoveRequest():
      final table = state.getTableOrDefault(event.position.table);
      final cell = table.getCell(event.position.position);
      final currentObject = cell.tiles[event.index];
      final definition = assetManager.getBoard(currentObject.asset);
      final size = definition?.tiles ?? VectorDefinition.one;
      final newTiles = <VectorDefinition, List<BoardTile>>{};
      for (var x = 0; x < size.x; x++) {
        for (var y = 0; y < size.y; y++) {
          final position = VectorDefinition(
            x + event.position.x - currentObject.tile.x,
            y + event.position.y - currentObject.tile.y,
          );
          final cell = table.getCell(position);
          final index = cell.tiles.indexWhere(
            (e) =>
                e.asset == currentObject.asset &&
                e.tile.x == x &&
                e.tile.y == y,
          );
          if (index != -1) {
            final newTilesList = List<BoardTile>.from(cell.tiles)
              ..removeAt(index);
            newTiles[position] = newTilesList;
          }
        }
      }
      return UpdateServerResponse.builder(
        BoardTilesChanged(event.position.table, newTiles),
        kAnyChannel,
      );
    case BoardMoveRequest():
      final table = state.getTableOrDefault(event.table);
      final from = table.getCell(event.from);
      final currentObject = from.tiles[event.index];
      final definition = assetManager.getBoard(currentObject.asset);
      final size = definition?.tiles ?? VectorDefinition.one;
      final newTiles = <VectorDefinition, List<BoardTile>>{};
      for (var x = 0; x < size.x; x++) {
        for (var y = 0; y < size.y; y++) {
          final fromPosition = VectorDefinition(
            x + event.from.x - currentObject.tile.x,
            y + event.from.y - currentObject.tile.y,
          );
          final toPosition = fromPosition + event.to - event.from;
          final tiles =
              newTiles[fromPosition] ?? table.getCell(fromPosition).tiles;
          final index = tiles.indexWhere(
            (e) =>
                e.asset == currentObject.asset &&
                e.tile.x == x &&
                e.tile.y == y,
          );
          if (index != -1) {
            final newTilesList = List<BoardTile>.from(tiles)..removeAt(index);
            newTiles[fromPosition] = newTilesList;
            newTiles
                .putIfAbsent(
                  toPosition,
                  () => List<BoardTile>.from(table.getCell(toPosition).tiles),
                )
                .add(BoardTile(currentObject.asset, VectorDefinition(x, y)));
          }
        }
      }
      return UpdateServerResponse.builder(
        BoardTilesChanged(event.table, newTiles),
        kAnyChannel,
      );
    case DialogCloseRequest():
      return UpdateServerResponse.builder(
        DialogsClosed.single(event.id),
        channel,
      );
    case ImagesRequest():
      return UpdateServerResponse.builder(
        ImagesUpdated(
          Map.fromEntries(
            event.ids.map((e) {
              final image = state.images[e];
              if (image == null) return null;
              return MapEntry(e, image);
            }).nonNulls,
          ),
        ),
        channel,
      );
    case ModeChangeRequest():
      final location = event.location;
      final mode = location == null
          ? null
          : assetManager.getPack(location.namespace)?.getMode(location.id);
      return UpdateServerResponse.builder(
        WorldInitialized.fromMode(mode, state),
        channel,
      );
    case AuthenticateRequest():
      final challenge = challengeManager?.getChallenge(channel);
      if (challenge == null) return null;
      if (challengeManager == null) return null;
      final verified = await event.verify(challenge);
      if (!verified) {
        return UpdateServerResponse.builder(
          AuthenticatedRequested(challenge, isRequired: true),
          channel,
        );
      }
      SetonixUser? user;
      try {
        user = await userManager?.addUser(
          channel,
          generateFingerprint(event.publicKey),
        );
      } catch (e) {
        if (e is KickMessage) {
          return KickServerResponse(e, kicked: {channel});
        } else {
          return KickServerResponse(
            KickMessage(reason: KickReason.notRegistered),
          );
        }
      }
      if (user == null) {
        final newChallenge = challengeManager.generateNewChallenge(channel);
        return UpdateServerResponse.builder(
          AuthenticatedRequested(newChallenge, isRequired: true),
          channel,
        );
      }
      return UpdateServerResponse.builder(buildInitialize(), channel);
  }
}