nil-core 0.8.5

Multiplayer strategy game
Documentation
// Copyright (C) Call of Nil contributors
// SPDX-License-Identifier: AGPL-3.0-only

use crate::chat::ChatMessage;
use crate::continent::coord::Coord;
use crate::error::{Error, Result};
use crate::player::PlayerId;
use crate::report::ReportKind;
use crate::round::Round;
use crate::world::config::WorldId;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::fmt;
use strum::Display;
use tokio::sync::broadcast::{Receiver, Sender, channel};
use uuid::Uuid;

pub type Listener = Receiver<(Bytes, EventTarget)>;

#[derive(Clone)]
pub(crate) struct Emitter {
  sender: Sender<(Bytes, EventTarget)>,
}

impl Emitter {
  pub fn new() -> Self {
    let (sender, _) = channel(100);
    Self { sender }
  }

  pub(crate) fn emit(&self, event: Event, target: EventTarget) -> Result<()> {
    tracing::trace!(?target, ?event);
    let bytes = Bytes::try_from(event)?;
    let _ = self.sender.send((bytes, target));
    Ok(())
  }

  pub(crate) fn emit_to(&self, target: PlayerId, event: Event) -> Result<()> {
    self.emit(event, EventTarget::Player(target))
  }

  pub(crate) fn broadcast(&self, event: Event) -> Result<()> {
    self.emit(event, EventTarget::Broadcast)
  }

  pub(crate) fn subscribe(&self) -> Listener {
    self.sender.subscribe()
  }
}

impl Default for Emitter {
  fn default() -> Self {
    Self::new()
  }
}

impl fmt::Debug for Emitter {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    f.debug_struct("Emitter")
      .field("sender", &self.sender.receiver_count())
      .finish()
  }
}

#[derive(Clone, Display, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export))]
#[remain::sorted]
pub enum Event {
  /// Uma nova mensagem foi enviada no chat.
  ChatMessage {
    id: EventId,
    world: WorldId,
    message: ChatMessage,
  },

  /// Indica que houve uma alteração nos dados da cidade, públicos ou privados.
  ///
  /// Este evento é emitido apenas para o dono da cidade.
  /// Se você acredita que todos os jogadores devem ser notificados,
  /// considere usar [`Event::PublicCity`].
  City {
    id: EventId,
    world: WorldId,
    coord: Coord,
  },

  /// Sinaliza que todos os jogadores ativos devem se desconectar, pois o mundo está prestes a ser descartado.
  ///
  /// Este evento **DEVE** ser emitido apenas dentro da implementação de `Drop` da struct `World`.
  Drop { id: EventId, world: WorldId },

  /// Indica que os preços do mercado mudaram.
  Market { id: EventId, world: WorldId },

  /// Indica que houve uma alteração nas forças militares do jogador.
  /// Geralmente significa que novas manobras foram iniciadas,
  /// pois os exércitos em si são processados no final da rodada.
  Military {
    id: EventId,
    world: WorldId,
    player: PlayerId,
  },

  /// Indica que houve uma alteração em alguns dados do jogador, públicos ou privados.
  Player {
    id: EventId,
    world: WorldId,
    player: PlayerId,
  },

  /// Indica que houve uma alteração nos dados públicos da cidade.
  ///
  /// Diferentemente de [`Event::City`], que é emitido apenas para o dono da cidade,
  /// este evento é enviado para todos os jogadores do mundo.
  ///
  /// Como regra, sempre que a situação exigir a emissão deste evento,
  /// [`Event::City`] também deve ser emitido, mas o contrário não é verdadeiro!
  PublicCity {
    id: EventId,
    world: WorldId,
    coord: Coord,
  },

  /// Um relatório foi gerado.
  Report {
    id: EventId,
    world: WorldId,
    report: Box<ReportKind>,
  },

  /// Indica alterações na rodada, como o fim do turno de um jogador ou
  /// a transição entre rodadas, depois que todos os jogadores concluíram suas ações.
  ///
  /// Quando emitido no início do jogo ou no final de uma rodada,
  /// [`Event::Round`] geralmente torna desnecessária a emissão de outros eventos,
  /// pois essa situação naturalmente faz com que todas as entidades sejam atualizadas.
  Round {
    id: EventId,
    world: WorldId,
    round: Round,
  },
}

impl fmt::Debug for Event {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    match self {
      Self::ChatMessage { id, world, message } => {
        f.debug_struct("ChatMessage")
          .field("id", id)
          .field("world", world)
          .field("message", &message.id())
          .finish()
      }
      Self::City { id, world, coord } => {
        f.debug_struct("City")
          .field("id", id)
          .field("world", world)
          .field("coord", coord)
          .finish()
      }
      Self::Drop { id, world } => {
        f.debug_struct("Drop")
          .field("id", id)
          .field("world", world)
          .finish()
      }
      Self::Market { id, world } => {
        f.debug_struct("Market")
          .field("id", id)
          .field("world", world)
          .finish()
      }
      Self::Military { id, world, player } => {
        f.debug_struct("Military")
          .field("id", id)
          .field("world", world)
          .field("player", player)
          .finish()
      }
      Self::Player { id, world, player } => {
        f.debug_struct("Player")
          .field("id", id)
          .field("world", world)
          .field("player", player)
          .finish()
      }
      Self::PublicCity { id, world, coord } => {
        f.debug_struct("PublicCity")
          .field("id", id)
          .field("world", world)
          .field("coord", coord)
          .finish()
      }
      Self::Report { id, world, report } => {
        f.debug_struct("Report")
          .field("id", id)
          .field("world", world)
          .field("report", &report.id())
          .finish()
      }
      Self::Round { id, world, round } => {
        f.debug_struct("Round")
          .field("id", id)
          .field("world", world)
          .field("round", &round.id())
          .finish()
      }
    }
  }
}

impl TryFrom<Bytes> for Event {
  type Error = Error;

  fn try_from(bytes: Bytes) -> Result<Self> {
    serde_json::from_slice(&bytes).map_err(|err| {
      tracing::error!("Failed to deserialize event: {err}");
      Error::FailedToDeserializeEvent
    })
  }
}

impl TryFrom<Event> for Bytes {
  type Error = Error;

  fn try_from(event: Event) -> Result<Self> {
    serde_json::to_vec(&event)
      .map(Bytes::from)
      .map_err(|err| {
        tracing::error!("Failed to serialize event: {err}");
        Error::FailedToSerializeEvent
      })
  }
}

#[derive(
  Clone,
  Copy,
  Debug,
  derive_more::Deref,
  derive_more::Display,
  PartialEq,
  Eq,
  PartialOrd,
  Ord,
  Hash,
  Deserialize,
  Serialize,
)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct EventId(Uuid);

impl EventId {
  #[must_use]
  pub fn new() -> Self {
    Self(Uuid::now_v7())
  }
}

impl Default for EventId {
  fn default() -> Self {
    Self::new()
  }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EventTarget {
  Broadcast,
  Player(PlayerId),
}