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