use crate::entities::tee::Tee;
use crate::entities::SpawnableEntity;
use crate::ids::{PlayerUid, TeamId};
use crate::state::globals::Globals;
use crate::state::Tees;
use crate::state::{EntityList, PrivilegedGameState};
use arrayvec::ArrayString;
use slotmap::SecondaryMap;
use twgame_core::net_msg::{ClPlayerInfo, Skin};
use twgame_core::twsnap;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::{SkinColor, Snap};
use twgame_core::Input;
#[derive(Debug)]
pub struct Players {
pub(super) players: SecondaryMap<PlayerUid, Player>,
}
impl Players {
pub(super) fn new() -> Self {
Self {
players: Default::default(),
}
}
pub(super) fn player_join(&mut self, now: Instant, player_uid: PlayerUid) {
self.players
.insert(player_uid, Player::new(now, player_uid));
}
pub(super) fn player_join_from_spectator(&mut self, player_uid: PlayerUid, mut player: Player) {
player.mark_spawn_at_end_of_tick(SpawnMode::Normal);
self.players.insert(player_uid, player);
}
pub(super) fn player_join_from_other_team(&mut self, player_uid: PlayerUid, player: Player) {
self.players.insert(player_uid, player);
}
pub(super) fn player_leave(&mut self, player_uid: PlayerUid) -> Option<Player> {
self.players.remove(player_uid)
}
pub(crate) fn get(&self, player_uid: PlayerUid) -> Option<&Player> {
self.players.get(player_uid)
}
pub(super) fn get_mut(&mut self, player_uid: PlayerUid) -> Option<&mut Player> {
self.players.get_mut(player_uid)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpawnMode {
Normal,
ForceWeak,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PauseMode {
Pause,
}
#[derive(Clone, Debug)]
pub struct Player {
pub(crate) name: ArrayString<15>,
pub(crate) clan: ArrayString<11>,
pub(crate) country: i32,
pub(crate) skin: ArrayString<23>,
pub(crate) use_custom_color: bool,
pub(crate) color_body: SkinColor,
pub(crate) color_feet: SkinColor,
player_uid: PlayerUid,
pub(super) tee: bool,
pub(super) input: Input,
pub(super) in_game: bool,
pub(super) spectator: bool,
pub(super) pause: Option<PauseMode>,
pub(super) spawning: Option<SpawnMode>,
pub(super) die_tick: Instant,
pub(super) previous_die_tick: Instant,
}
fn skin_color(c: i32) -> SkinColor {
let [h, s, l_upper, a] = c.to_le_bytes();
SkinColor { h, s, l_upper, a }
}
impl Player {
pub(super) fn new(now: Instant, player_uid: PlayerUid) -> Player {
Player {
name: ArrayString::new(),
clan: ArrayString::new(),
country: 0,
skin: ArrayString::new(),
use_custom_color: false,
color_body: SkinColor::default(),
color_feet: SkinColor::default(),
player_uid,
tee: false,
input: Input::new(),
in_game: false,
spectator: false,
pause: None,
spawning: None,
die_tick: now,
previous_die_tick: now,
}
}
pub(super) fn should_spawn(&self, spawn_mode: SpawnMode) -> bool {
self.spawning == Some(spawn_mode) && !self.spectator
}
pub(super) fn spawned(&mut self) {
self.tee = true;
self.spawning = None;
}
pub(super) fn on_player_info(&mut self, player_info: &ClPlayerInfo) {
self.name = ArrayString::from(String::from_utf8_lossy(player_info.name).as_ref())
.unwrap_or_default();
self.clan = ArrayString::from(String::from_utf8_lossy(player_info.clan).as_ref())
.unwrap_or_default();
self.country = player_info.country;
match &player_info.skin {
Skin::V6(skin) => {
self.skin = ArrayString::from(String::from_utf8_lossy(skin.skin).as_ref())
.unwrap_or_default();
self.use_custom_color = skin.use_custom_color;
self.color_body = skin_color(skin.color_body);
self.color_feet = skin_color(skin.color_feet);
}
Skin::V7(_) => {
}
}
}
pub(super) fn on_input(&mut self) {
if !self.tee && !self.spectator && self.input.fire & 1 != 0 {
self.mark_spawn_at_end_of_tick(SpawnMode::Normal)
}
}
pub(super) fn kill_tee(
&mut self,
now: Instant,
tees: &mut Tees<Tee>,
spawn_mode: SpawnMode,
) -> bool {
if !self.tee {
return false;
};
let Some(tee) = tees.get_tee_mut(self.player_uid) else {
return false;
};
if tee.is_marked_for_destroy() {
return false;
}
tee.mark_for_destroy();
self.mark_spawn_at_end_of_tick(spawn_mode);
self.previous_die_tick = self.die_tick;
self.die_tick = now;
true
}
pub(super) fn switch_team(
&mut self,
from_team: &mut PrivilegedGameState,
to_team: &mut PrivilegedGameState,
from_entities: &mut EntityList,
to_entities: Option<&mut EntityList>,
) {
if self.tee {
from_team.move_player(to_team, self.player_uid, from_entities, to_entities);
}
}
pub(super) fn snap(&self, team_id: TeamId, player_uid: PlayerUid, snap: &mut Snap) {
let player = snap.players.get_mut_default(player_uid.snap_id());
player.team = team_id.to_i32();
player.local = false;
player.teeworlds_team = twsnap::enums::ClientTeam::Red;
player.score = 0;
player.latency = 0;
player.name = self.name;
player.clan = self.clan;
player.country = self.country;
player.skin = self.skin;
player.use_custom_color = self.use_custom_color;
player.color_body = self.color_body;
player.color_feet = self.color_feet;
}
}
impl Player {
pub(super) fn mark_spawn_at_end_of_tick(&mut self, spawn_mode: SpawnMode) {
if !self.spectator {
self.spawning = Some(spawn_mode);
}
}
pub(super) fn tick(&mut self, now: Instant, tees: &Tees<Tee>, globals: &Globals) {
if !self.in_game {
return; }
if self.tee {
let tee = tees.get_tee(self.player_uid);
if tee.map(|tee| tee.is_marked_for_destroy()).unwrap_or(true) {
self.tee = false;
self.previous_die_tick = self.die_tick;
self.die_tick = now;
if globals.kill_propagates() && self.spawning.is_none() {
self.spawning = Some(SpawnMode::ForceWeak)
}
} else {
}
} else if self.spawning.is_none() {
let earliest_respawn_tick = self.previous_die_tick + Duration::from_secs(3);
let respawn_tick = earliest_respawn_tick.max(self.die_tick) + Duration::T40MS;
if respawn_tick <= now {
self.mark_spawn_at_end_of_tick(SpawnMode::Normal)
}
}
}
}