Skip to main content

nil_core/
player.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::error::{Error, Result};
5use crate::resources::Resources;
6use crate::resources::influence::Influence;
7use crate::ruler::Ruler;
8use crate::world::World;
9use bon::Builder;
10use derive_more::{From, Into};
11use serde::{Deserialize, Serialize};
12use std::borrow::{Borrow, Cow};
13use std::collections::HashMap;
14use std::ops::Deref;
15use std::sync::Arc;
16
17#[derive(Clone, Debug, Default, Deserialize, Serialize)]
18pub struct PlayerManager(HashMap<PlayerId, Player>);
19
20impl PlayerManager {
21  pub(crate) fn manage(&mut self, player: Player) -> Result<()> {
22    if self.0.contains_key(&player.id) {
23      return Err(Error::PlayerAlreadySpawned(player.id));
24    } else {
25      self.0.insert(player.id(), player);
26    }
27
28    Ok(())
29  }
30
31  pub fn player(&self, id: &PlayerId) -> Result<&Player> {
32    self
33      .0
34      .get(id)
35      .ok_or_else(|| Error::PlayerNotFound(id.clone()))
36  }
37
38  pub(crate) fn player_mut(&mut self, id: &PlayerId) -> Result<&mut Player> {
39    self
40      .0
41      .get_mut(id)
42      .ok_or_else(|| Error::PlayerNotFound(id.clone()))
43  }
44
45  pub fn players(&self) -> impl Iterator<Item = &Player> {
46    self.0.values()
47  }
48
49  pub fn player_ids(&self) -> impl Iterator<Item = &PlayerId> {
50    self.0.keys()
51  }
52
53  pub fn active_players(&self) -> impl Iterator<Item = &Player> {
54    self
55      .players()
56      .filter(|player| player.is_active())
57  }
58
59  #[inline]
60  pub fn has(&self, id: &PlayerId) -> bool {
61    self.0.contains_key(id)
62  }
63}
64
65#[derive(Clone, Debug, Deserialize, Serialize)]
66#[serde(rename_all = "camelCase")]
67#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
68pub struct Player {
69  id: PlayerId,
70  status: PlayerStatus,
71  resources: Resources,
72  influence: Influence,
73}
74
75impl Player {
76  pub fn new(options: PlayerOptions) -> Self {
77    Self {
78      id: options.id,
79      status: PlayerStatus::Active,
80      resources: Resources::PLAYER.clone(),
81      influence: Influence::MIN,
82    }
83  }
84
85  pub fn spawn(self, world: &mut World) -> Result<()> {
86    world.spawn_player(self)
87  }
88
89  #[inline]
90  pub fn id(&self) -> PlayerId {
91    self.id.clone()
92  }
93
94  #[inline]
95  pub fn status(&self) -> PlayerStatus {
96    self.status
97  }
98
99  pub(crate) fn status_mut(&mut self) -> &mut PlayerStatus {
100    &mut self.status
101  }
102
103  #[inline]
104  pub fn resources(&self) -> &Resources {
105    &self.resources
106  }
107
108  pub(crate) fn resources_mut(&mut self) -> &mut Resources {
109    &mut self.resources
110  }
111
112  #[inline]
113  pub fn influence(&self) -> Influence {
114    self.influence
115  }
116
117  #[inline]
118  pub fn is_active(&self) -> bool {
119    matches!(self.status, PlayerStatus::Active)
120  }
121
122  #[inline]
123  pub fn is_inactive(&self) -> bool {
124    matches!(self.status, PlayerStatus::Inactive)
125  }
126}
127
128#[derive(
129  Debug,
130  derive_more::Display,
131  From,
132  Into,
133  PartialEq,
134  Eq,
135  PartialOrd,
136  Ord,
137  Hash,
138  Deserialize,
139  Serialize,
140)]
141#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
142#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
143pub struct PlayerId(Arc<str>);
144
145impl Clone for PlayerId {
146  fn clone(&self) -> Self {
147    Self(Arc::clone(&self.0))
148  }
149}
150
151impl AsRef<str> for PlayerId {
152  fn as_ref(&self) -> &str {
153    self.0.as_str()
154  }
155}
156
157impl Deref for PlayerId {
158  type Target = str;
159
160  fn deref(&self) -> &Self::Target {
161    self.0.as_str()
162  }
163}
164
165impl Borrow<str> for PlayerId {
166  fn borrow(&self) -> &str {
167    self.0.as_str()
168  }
169}
170
171impl PartialEq<Ruler> for PlayerId {
172  fn eq(&self, other: &Ruler) -> bool {
173    if let Ruler::Player { id } = other { self.eq(id) } else { false }
174  }
175}
176
177#[derive(Clone, Copy, Debug, strum::Display, PartialEq, Eq, Deserialize, Serialize)]
178#[serde(rename_all = "kebab-case")]
179#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
180pub enum PlayerStatus {
181  Active,
182  Inactive,
183}
184
185#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
186#[serde(rename_all = "camelCase")]
187#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
188pub struct PlayerOptions {
189  #[builder(start_fn, into)]
190  pub id: PlayerId,
191}
192
193impl PlayerOptions {
194  #[inline]
195  pub fn into_player(self) -> Player {
196    Player::new(self)
197  }
198}
199
200#[derive(Clone, Debug, Deserialize, Serialize)]
201#[serde(rename_all = "camelCase")]
202#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
203pub struct PublicPlayer {
204  id: PlayerId,
205  status: PlayerStatus,
206}
207
208impl From<&Player> for PublicPlayer {
209  fn from(player: &Player) -> Self {
210    Self {
211      id: player.id.clone(),
212      status: player.status,
213    }
214  }
215}