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