use crate::entities::tee::TEE_PROXIMITY;
use crate::entities::tee::{Tee, TeeCore};
use crate::entities::{
EntityItem, EntityItemNew, MapEntity, MapEntityItem, SpawnOrder, SpawnOrderEntity,
SpawnableEntity,
};
use crate::ids::{PlayerUid, TeamId};
use crate::map::Map;
use crate::teams::{Activity, Client};
use crate::{coord, SnapOuter};
use bugs::Bugs;
use database::Database;
use entities::Entities;
use globals::Globals;
use rand_pcg::Lcg64Xsh32;
use slotmap::SecondaryMap;
use std::cell::RefCell;
use std::fmt::Write;
use std::mem;
use std::rc::Rc;
use std::sync::{mpsc, Arc};
use twgame_core::database::FinishTeam;
use twgame_core::database::{DatabaseWrite, SaveTeam};
use twgame_core::net_msg::ClPlayerInfo;
use twgame_core::normalize;
use twgame_core::twsnap;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::{vec2_from_bits, Snap};
use twgame_core::Input;
use uuid::Uuid;
use vek::num_traits::clamp;
use vek::Vec2;
mod bugs;
mod database;
mod entities;
mod events;
mod globals;
mod players;
mod prng;
mod tee_order;
mod tees;
pub use bugs::{Bug, BugKind};
pub use events::Events;
pub use globals::TeamKind;
pub use players::PauseMode;
pub use players::Player;
pub use players::Players;
pub use players::SpawnMode;
pub use prng::Prng;
pub use tee_order::TeeOrder;
pub use tees::closest_point_on_line;
pub use tees::Tees;
#[derive(Debug)]
pub struct PrivilegedGameState {
id: TeamId,
inner: GameState,
tee_order: TeeOrder,
tees: Tees<Tee>,
players: Players,
map_entities: Vec<MapEntityItem>,
}
#[derive(Debug)]
pub struct GameState {
pub map: Rc<Arc<Map>>,
pub globals: Globals,
pub tee_cores: Tees<TeeCore>,
pub tee_order: TeeOrder,
pub entities: Entities,
pub prng: Prng,
pub events: Events,
pub bugs: Bugs,
pub database: Database,
}
impl PrivilegedGameState {
pub fn new(
map: Rc<Arc<Map>>,
team_id: TeamId,
team_kind: TeamKind,
prng_description: Lcg64Xsh32,
database_sender: mpsc::Sender<DatabaseWrite>,
) -> Self {
let map_entities = map.entities.clone();
Self {
id: team_id,
inner: GameState {
map,
tee_cores: Tees::new(),
tee_order: TeeOrder::new(),
entities: Entities::new(team_id),
prng: Prng::new(team_id, prng_description),
events: Events::new(),
globals: Globals::new(team_id, team_kind),
bugs: Bugs::new(),
database: database::Database::new(database_sender),
},
tees: Tees::new(),
tee_order: TeeOrder::new(),
players: Players::new(),
map_entities,
}
}
pub fn create_new(&self, team_id: TeamId, compat: bool) -> Self {
let mut team_kind = self.inner.globals.team_kind();
if team_kind == TeamKind::Team0 {
team_kind = TeamKind::NonTeam0;
}
Self {
id: team_id,
inner: GameState {
map: self.inner.map.clone(),
tee_cores: Tees::new(),
tee_order: TeeOrder::new(),
entities: if compat {
Entities::new_compat(team_id, self.inner.entities.clone_next_tick())
} else {
Entities::new(team_id)
},
prng: self.inner.prng.create_new(team_id),
events: Events::new(),
globals: Globals::new(team_id, team_kind),
bugs: self.inner.bugs.create_new(),
database: Database::new(self.inner.database.clone_sender()),
},
tees: Tees::new(),
tee_order: TeeOrder::new(),
players: Players::new(),
map_entities: self.inner.map.entities.clone(),
}
}
pub(crate) fn entities_next_tick(&self) -> Rc<RefCell<Vec<(TeamId, EntityItemNew)>>> {
self.inner.entities.clone_next_tick()
}
pub(crate) fn overwrite_prng(&mut self, prng_description: Lcg64Xsh32) {
self.inner.prng.overwrite(prng_description);
}
pub(crate) fn supply_prng_compat_data(&mut self, compat_data: &str) {
self.inner.prng.supply_compat_data(compat_data);
}
pub(crate) fn enable_prng_compat_data_collection(&mut self) {
self.inner.prng.enable_compat_data_collection();
}
pub(crate) fn retrieve_prng_compat_data(&mut self) -> Option<String> {
self.inner.prng.retrieve_compat_data()
}
pub(crate) fn retrieve_bugs(&self) -> Vec<Bug> {
self.inner.bugs.retrieve_bugs()
}
pub(crate) fn clear_events(&mut self) {
self.inner.events.clear();
}
pub(crate) fn snap(&self, team_id: TeamId, snapshot: &mut Snap) {
for entity in self.map_entities.iter() {
entity.snap(snapshot);
}
for (player_uid, player) in self.players.players.iter() {
player.snap(team_id, player_uid, snapshot);
}
snapshot.events.extend_from_slice(&self.inner.events.events);
}
pub(crate) fn toggle_lock(&mut self) {
self.inner.globals.toggle_lock();
}
pub(crate) fn set_lock(&mut self, locked: bool) {
self.inner.globals.set_lock(locked);
}
pub(crate) fn is_locked(&self) -> bool {
self.inner.globals.is_locked()
}
pub(crate) fn can_load(&self) -> bool {
self.inner.globals.can_load()
}
fn match_tee(&mut self, player_name: &str) -> Option<(&mut Player, &mut Tee, &mut TeeCore)> {
for (player_uid, player) in self.players.players.iter_mut() {
if player.name.as_str() == player_name {
let tee = self
.tees
.get_tee_mut(player_uid)
.expect("handle tee not living during load");
let tee_core = self
.inner
.tee_cores
.get_tee_mut(player_uid)
.expect("Tee exists, but TeeCore doesn't");
return Some((player, tee, tee_core));
}
}
None
}
pub(crate) fn load(&mut self, now: Instant, save_team: SaveTeam) {
save_team.pretty_print();
let mut items = save_team.buf().split('\n');
let mut team_info = items.next().unwrap().split('\t');
let _team_state: i32 = team_info.next().unwrap().parse().unwrap();
let members_count: i32 = team_info.next().unwrap().parse().unwrap();
let _highest_switch_number: i32 = team_info.next().unwrap().parse().unwrap();
let team_locked: i32 = team_info.next().unwrap().parse().unwrap();
let _practice: i32 = team_info
.next()
.map(|p| p.parse::<i32>().unwrap())
.unwrap_or(0);
let mut uuid = Uuid::new_v4();
let mut start_time = None;
for _ in 0..members_count {
let mut tee_info = items.next().unwrap().split('\t');
let player_name = tee_info.next().unwrap();
if let Some((_player, tee, tee_core)) = self.match_tee(player_name) {
uuid = tee.load(now, tee_core, tee_info);
if let Some(tee_start) = tee.start_time() {
if let Some(team_start) = start_time {
if tee_start < team_start {
start_time = Some(tee_start)
}
} else {
start_time = Some(tee_start)
}
}
}
}
self.inner.globals.load(team_locked != 0, start_time);
let save = SaveTeam::from_string(self.save(now, uuid, false));
save.print_diff(&save_team);
}
pub(crate) fn can_save(&self) -> bool {
self.inner.globals.can_save()
}
pub(crate) fn save(&self, now: Instant, game_uuid: Uuid, add_time_penalty: bool) -> String {
let mut f = String::with_capacity(4096);
let team_state = 2;
let members_count = self.inner.tee_order.member_count();
let highest_switch_number = 0;
let team_locked = self.is_locked() as i32;
let practice = false as i32;
write!(
f,
"{team_state}\t{members_count}\t{highest_switch_number}\t{team_locked}\t{practice}"
)
.unwrap();
for tee_uid in self.tee_order.spawn_order() {
let tee = self.tees.get_tee(tee_uid).unwrap();
let tee_core = self.inner.tee_cores.get_tee(tee_uid).unwrap();
let player = self.players.get(tee.player_uid()).unwrap();
tee.format_save(now, player, tee_core, &mut f, game_uuid, add_time_penalty)
.unwrap();
}
f
}
}
impl GameState {
fn evaluate_spawn_point(&self, point: Vec2<f32>) -> f32 {
self.tee_order
.spawn_order()
.tees(&self.tee_cores)
.map(|(_, tee)| 1.0 / point.distance(tee.pos()))
.sum()
}
fn evaluate_spawn_points(&self, spawn_points: &[Vec2<i32>]) -> Option<(Vec2<i32>, f32)> {
let mut best_spawn: Option<(Vec2<i32>, f32)> = None;
for check_occupied in [true, false] {
for spawn_point in spawn_points {
let positions = [
Vec2::new(0, 0),
Vec2::new(-1, 0),
Vec2::new(0, -1),
Vec2::new(1, 0),
Vec2::new(0, 1),
];
for p in positions.iter() {
let spawn_candidate = spawn_point + p;
let spawn_candidate_float =
coord::to_float(spawn_candidate) + Vec2::new(16.0, 16.0);
if self.map.is_solid(spawn_candidate) {
continue;
}
if check_occupied {
let occupied = self
.tee_order
.spawn_order()
.tees(&self.tee_cores)
.any(|(_, tee)| tee.collides_point(spawn_candidate_float));
if occupied {
continue;
}
}
let score = self.evaluate_spawn_point(spawn_candidate_float);
if let Some(s) = &best_spawn {
if s.1 > score {
best_spawn = Some((spawn_candidate, score));
}
} else {
best_spawn = Some((spawn_candidate, score));
}
break; }
}
if best_spawn.is_some() {
return best_spawn;
}
}
None
}
pub(crate) fn get_spawn_point(&self) -> Option<Vec2<f32>> {
self.map
.spawn_points
.iter()
.filter_map(|spawn_points| self.evaluate_spawn_points(spawn_points))
.reduce(|a, b| if a.1 <= b.1 { a } else { b })
.map(|(pos, _score)| coord::to_float(pos) + Vec2::new(16.0, 16.0))
}
pub(crate) fn select_tele_checkpoint_out(&mut self, now: Instant, tele_in: u8) -> Vec2<f32> {
for k in (1..=tele_in).rev() {
if self.map.tele_checkpoint_outs[k as usize].is_empty() {
continue;
}
let tele_out = self
.prng
.random_or_0(now, self.map.tele_checkpoint_outs[k as usize].len() as u32);
return coord::to_float(self.map.tele_checkpoint_outs[k as usize][tele_out as usize])
+ Vec2::new(16.0, 16.0);
}
self.get_spawn_point().unwrap()
}
pub fn create_explosion(&mut self, owner: PlayerUid, pos: Vec2<f32>, strength: f32) {
let owner_solo = self
.tee_cores
.get_tee(owner)
.map(|tee| tee.is_solo())
.unwrap_or(false);
let mut iter = self.tee_order.spawn_order().tees_mut(&mut self.tee_cores);
while let Some((tee_uid, tee)) = iter.next() {
if tee_uid != owner && (owner_solo || tee.is_solo()) || tee.pos().distance(pos) >= TEE_PROXIMITY + 135.0
{
continue;
}
let diff = tee.pos() - pos;
let mut force_dir = Vec2::new(0.0, 1.0);
let mut l = diff.magnitude();
if l != 0.0 {
force_dir = normalize(diff);
}
l = 1.0 - clamp((l - 48.0) / (135.0 - 48.0), 0.0, 1.0);
let dmg = strength * l;
if dmg as i32 == 0 {
continue;
}
tee.impact(force_dir * dmg * 2.0, true);
}
self.events
.add_event(twsnap::Events::Explosion(twsnap::events::Explosion {
pos: vec2_from_bits(pos),
}));
}
pub fn on_tee_start(&mut self, now: Instant) {
if self.globals.team_kind() == TeamKind::Team0 {
return;
}
if self.globals.start_time.is_none() {
self.globals.start_time = Some(now);
}
}
pub fn on_tee_finish(&mut self, now: Instant, start_time: Instant, tee_uid: PlayerUid) {
if self.globals.team_kind() == TeamKind::Team0 {
let time = now.duration_since(start_time).unwrap();
let Some(name) = Some("Zwelf") else { todo!() };
self.database.add(DatabaseWrite::finish_tee(name, time));
} else if let Some(team_start_time) = self.globals.start_time {
self.globals.all_finished = self
.tee_order
.spawn_order()
.tees_except(&self.tee_cores, tee_uid)
.all(|(_, tee)| tee.has_finished());
if self.globals.all_finished {
let time = now.duration_since(team_start_time).unwrap();
self.database.add(DatabaseWrite::FinishTeam(FinishTeam {
team: self.globals.team_id().to_i32(),
names: vec!["Zwelf".to_owned()],
time,
}))
}
}
}
}
pub(crate) trait AssociatedGameState {
fn get_game_state(&self, team_id: TeamId) -> &PrivilegedGameState;
}
pub(crate) trait AssociatedGameStateMut: AssociatedGameState {
fn get_game_state_mut(&mut self, team_id: TeamId) -> &mut PrivilegedGameState;
}
impl AssociatedGameState for PrivilegedGameState {
fn get_game_state(&self, team_id: TeamId) -> &PrivilegedGameState {
assert_eq!(team_id, self.id);
self
}
}
impl AssociatedGameStateMut for PrivilegedGameState {
fn get_game_state_mut(&mut self, team_id: TeamId) -> &mut PrivilegedGameState {
assert_eq!(team_id, self.id);
self
}
}
#[derive(Debug)]
pub(crate) struct EntityList {
entities: Vec<(TeamId, EntityItem)>,
next_tick: Rc<RefCell<Vec<(TeamId, EntityItemNew)>>>,
}
impl EntityList {
pub(crate) fn new(entities_next_tick: Rc<RefCell<Vec<(TeamId, EntityItemNew)>>>) -> Self {
Self {
entities: vec![],
next_tick: entities_next_tick,
}
}
pub(crate) fn snap(
&self,
now: Instant,
game_state_provider: &dyn AssociatedGameState,
snapshot: &mut Snap,
) {
for (team_id, entity) in self.entities.iter() {
let game_state = game_state_provider.get_game_state(*team_id);
entity.snap(now, &game_state.inner, snapshot, &game_state.tees);
}
}
pub(crate) fn is_empty(&self) -> bool {
self.entities.is_empty()
}
pub(crate) fn on_swap_tees(
&mut self,
game_state: &mut PrivilegedGameState,
team_id: TeamId,
pid1: PlayerUid,
pid2: PlayerUid,
) {
for (tid, entity) in self.entities.iter_mut() {
if team_id == *tid {
entity.on_tee_swap(&mut game_state.tees, pid1, pid2);
}
}
}
}
impl PrivilegedGameState {
pub(crate) fn player_join(&mut self, now: Instant, player_uid: PlayerUid) -> PlayerUid {
self.players.player_join(now, player_uid);
player_uid
}
pub(crate) fn player_join_from_spectator(&mut self, player_uid: PlayerUid, player: Player) {
self.players.player_join_from_spectator(player_uid, player);
}
pub(crate) fn player_join_from_other_team(
&mut self,
player_uid: PlayerUid,
old_team: &mut PrivilegedGameState,
from_entities: &mut EntityList,
to_entities: Option<&mut EntityList>,
) {
let mut player = old_team.players.player_leave(player_uid).unwrap();
player.switch_team(old_team, self, from_entities, to_entities);
self.players.player_join_from_other_team(player_uid, player);
}
pub(crate) fn player_join_team0_from_other_team(
&mut self,
player_uid: PlayerUid,
mut player: Player,
) {
if !player.tee {
player.mark_spawn_at_end_of_tick(SpawnMode::Normal);
}
self.players.player_join_from_other_team(player_uid, player);
}
pub(crate) fn player_ready(&mut self, player_uid: PlayerUid) {
let player = self.players.get_mut(player_uid).unwrap();
player.in_game = true;
player.mark_spawn_at_end_of_tick(SpawnMode::Normal);
}
pub(crate) fn player_input(&mut self, player_uid: PlayerUid, input: &Input) {
let p = self.players.get_mut(player_uid).unwrap();
p.input = input.clone();
if let Some(tee) = self.tees.get_tee_mut(player_uid) {
tee.received_input()
}
}
pub(crate) fn start_info(&mut self, player_uid: PlayerUid, player_info: &ClPlayerInfo) {
if let Some(p) = self.players.get_mut(player_uid) {
p.on_player_info(player_info);
}
}
pub(crate) fn change_info(&mut self, player_uid: PlayerUid, player_info: &ClPlayerInfo) {
if let Some(p) = self.players.get_mut(player_uid) {
p.on_player_info(player_info);
}
}
pub(crate) fn player_leave(&mut self, player_uid: PlayerUid) -> Player {
let player = self.players.player_leave(player_uid).unwrap();
if let Some(tee) = self.tees.get_tee_mut(player_uid) {
tee.mark_for_destroy();
}
player
}
pub(crate) fn tee_pos(&self, player_uid: PlayerUid) -> Option<Vec2<i32>> {
let tee = self.inner.tee_cores.get_tee(player_uid)?;
Some(tee.get_tee_pos())
}
pub(crate) fn set_tee_pos(
&mut self,
entity_list: &mut EntityList,
now: Instant,
player_uid: PlayerUid,
pos: Option<Vec2<i32>>,
) {
let player_tee = self.inner.tee_cores.get_tee_mut(player_uid);
match (pos, player_tee) {
(Some(pos), Some(tee)) => {
tee.set_tee_pos(pos);
}
(Some(pos), None) => {
let p = self.players.get_mut(player_uid).unwrap();
p.tee = true;
p.spawning = None;
let input = p.input.clone();
self.spawn_tee(
entity_list,
player_uid,
now,
Vec2::new(pos.x as f32, pos.y as f32),
SpawnMode::Normal,
input,
);
}
(None, Some(_)) => {
self.kill_tee(now, player_uid, SpawnMode::Normal);
}
(None, None) => {}
}
}
pub(crate) fn kill_tee(
&mut self,
now: Instant,
player_uid: PlayerUid,
spawn_mode: SpawnMode,
) -> bool {
let Some(tee) = self.tees.get_tee_mut(player_uid) else {
return false;
};
if !tee.is_marked_for_destroy() {
let player = self.players.get_mut(player_uid).unwrap();
tee.mark_for_destroy();
player.mark_spawn_at_end_of_tick(spawn_mode);
player.previous_die_tick = player.die_tick;
player.die_tick = now;
return true;
}
false
}
pub(crate) fn tick_0_fire_direct(&mut self, now: Instant, snap_outer: &mut SnapOuter) {
for (player_uid, player) in self.players.players.iter_mut() {
player.on_input();
if player.tee {
let Some(tee) = self.tees.get_tee_mut(player_uid) else {
continue;
};
if player.pause.is_none() && !player.input.spec_cam_active() {
tee.store_new_input(player.input.clone());
}
tee.on_direct_input(now, &mut self.inner, snap_outer);
}
}
}
}
impl EntityList {
fn add_new_entities(&mut self, _game_state_provider: &dyn AssociatedGameStateMut) {
for (team_id, e) in self.next_tick.borrow_mut().drain(..) {
let new = match e {
EntityItemNew::Laser(laser) => EntityItem::Laser(laser),
EntityItemNew::Projectile(projectile) => EntityItem::Projectile(projectile),
};
self.entities.push((team_id, new));
}
}
}
impl PrivilegedGameState {
pub(crate) fn tick_1_map_entities(&mut self, now: Instant) {
for entity in self.map_entities.iter_mut().rev() {
entity.tick(now, &mut self.inner, &mut self.tees);
}
}
}
impl EntityList {
pub(crate) fn tick_2_main_logic(
&mut self,
now: Instant,
game_state_provider: &mut dyn AssociatedGameStateMut,
snap_outer: &mut SnapOuter,
) {
self.add_new_entities(game_state_provider);
for (team_id, entity) in self.entities.iter_mut().rev() {
let game_state = game_state_provider.get_game_state_mut(*team_id);
entity.tick(now, &mut game_state.inner, &mut game_state.tees, snap_outer);
}
}
}
impl PrivilegedGameState {
pub(crate) fn tick_3_should_dissolve(&self) -> bool {
self.inner.globals.all_finished() && !self.inner.globals.is_locked()
}
pub(crate) fn tick_4_move_tees(&mut self, now: Instant) {
for tee_uid in self.tee_order.spawn_order() {
let tee = self.tees.get_tee_mut(tee_uid).unwrap();
tee.post_tick(now, &mut self.inner);
}
}
}
impl EntityList {
pub(crate) fn tick_5_remove_destroyed(
&mut self,
game_state_provider: &mut dyn AssociatedGameStateMut,
clients: &mut SecondaryMap<PlayerUid, Client>,
mut to_team0: Option<&mut PrivilegedGameState>,
) {
self.add_new_entities(game_state_provider);
let mut removed_tees = vec![];
self.entities.retain(|(team_id, entity)| {
let game_state = game_state_provider.get_game_state_mut(*team_id);
let mut remove = entity.is_marked_for_destroy(&game_state.tees);
remove |= removed_tees.contains(&entity.player_uid());
if remove {
if let EntityItem::Tee(tee_uid) = entity {
removed_tees.push(*tee_uid);
game_state.tees.remove_tee(*tee_uid).expect("Tee existed");
game_state.tee_order.remove_tee(*tee_uid);
game_state
.inner
.tee_cores
.remove_tee(*tee_uid)
.expect("TeeCore existed");
game_state.inner.tee_order.remove_tee(*tee_uid);
game_state.tees.reset_hook(*tee_uid);
if game_state.inner.globals.killed_to_team0() {
if let Some(player) = game_state.players.player_leave(*tee_uid) {
clients.get_mut(*tee_uid).unwrap().activity =
Activity::Playing(TeamId::team0());
let team0 = to_team0.as_deref_mut().unwrap_or_else(|| {
game_state_provider.get_game_state_mut(TeamId::team0())
});
team0.player_join_team0_from_other_team(*tee_uid, player);
}
}
}
}
!remove
});
}
}
impl PrivilegedGameState {
pub(crate) fn tick_6_is_empty(&self) -> bool {
self.players.players.is_empty()
}
pub(crate) fn post_tick(&mut self, entity_list: &mut EntityList, now: Instant) {
let mut players = Default::default();
mem::swap(&mut self.players.players, &mut players);
for (player_uid, p) in players.iter_mut() {
p.tick(now, &self.tees, &self.inner.globals);
if p.should_spawn(SpawnMode::Normal)
&& self.try_spawn_tee(
entity_list,
player_uid,
now,
SpawnMode::Normal,
p.input.clone(),
)
{
p.spawned();
}
}
for (player_uid, p) in players.iter_mut() {
if p.should_spawn(SpawnMode::ForceWeak)
&& self.try_spawn_tee(
entity_list,
player_uid,
now,
SpawnMode::ForceWeak,
p.input.clone(),
)
{
p.spawned();
}
}
mem::swap(&mut self.players.players, &mut players);
}
pub(crate) fn spawn_tee(
&mut self,
entity_list: &mut EntityList,
player_uid: PlayerUid,
now: Instant,
pos: Vec2<f32>,
spawn_mode: SpawnMode,
input: Input,
) {
let spawn_order = SpawnOrder::new(player_uid, now, SpawnOrderEntity::Tee(spawn_mode));
let mut tee_core = TeeCore::new(pos);
let tee = Tee::new(player_uid, spawn_order, &mut tee_core, input);
self.tees.add_tee(player_uid, tee);
self.tee_order.add_tee(player_uid, spawn_order);
entity_list
.entities
.push((self.id, EntityItem::Tee(player_uid)));
self.inner.tee_cores.add_tee(player_uid, tee_core);
self.inner.tee_order.add_tee(player_uid, spawn_order);
self.inner
.events
.add_event(twsnap::Events::Spawn(twsnap::events::Spawn {
pos: vec2_from_bits(pos),
}));
}
pub(crate) fn try_spawn_tee(
&mut self,
entity_list: &mut EntityList,
player_uid: PlayerUid,
now: Instant,
spawn_mode: SpawnMode,
input: Input,
) -> bool {
if let Some(pos) = self.inner.get_spawn_point() {
self.spawn_tee(entity_list, player_uid, now, pos, spawn_mode, input);
true
} else {
false
}
}
pub(crate) fn move_player(
&mut self,
to: &mut Self,
tee_uid: PlayerUid,
from_entities: &mut EntityList,
to_entities: Option<&mut EntityList>,
) {
if let Some(tee) = self.tees.remove_tee(tee_uid) {
let tee_core = self.inner.tee_cores.remove_tee(tee_uid).unwrap();
self.inner.tee_order.remove_tee(tee_uid);
self.tee_order.remove_tee(tee_uid);
if to_entities.is_none() {
from_entities.entities.retain_mut(|(team_id, e)| {
if e.player_uid() == tee_uid {
if matches!(e, EntityItem::Tee(_)) {
*team_id = to.id();
true
} else {
false
}
} else {
true
}
});
} else {
from_entities
.entities
.retain(|(_, e)| e.player_uid() != tee_uid);
}
let spawn_order = tee.spawn_order();
to.inner.tee_cores.add_tee(tee.player_uid(), tee_core);
to.tees.add_tee(tee_uid, tee);
to.inner
.tee_order
.add_tee_from_other_team(tee_uid, spawn_order);
to.tee_order.add_tee_from_other_team(tee_uid, spawn_order);
if let Some(to_entities) = to_entities {
let pos = to_entities
.entities
.binary_search_by(|(_, e)| e.spawn_order(&to.tees).cmp(&spawn_order))
.unwrap_err();
to_entities
.entities
.insert(pos, (to.id, EntityItem::Tee(tee_uid)));
}
}
}
pub(crate) fn swap_tees(&mut self, p1_uid: PlayerUid, p2_uid: PlayerUid) {
let [t1, t2] = self.tees.get_tees_mut([p1_uid, p2_uid]).unwrap();
t1.swap(t2);
let [tee_core1, tee_core2] = self.inner.tee_cores.get_tees_mut([p1_uid, p2_uid]).unwrap();
mem::swap(tee_core1, tee_core2);
}
pub(crate) fn kill(
&mut self,
player_uid: PlayerUid,
now: Instant,
within_kill_protection: bool,
) {
let Some(p) = self.players.get_mut(player_uid) else {
return;
};
if !p.tee {
return;
}
let time = self
.inner
.globals
.race_time(now, &self.tees, player_uid)
.unwrap_or(Duration::T0MS);
let kill_protection = time >= Duration::from_min(20);
if kill_protection != within_kill_protection {
return;
}
if p.kill_tee(now, &mut self.tees, SpawnMode::Normal)
&& self.inner.globals.kill_propagates()
{
for (p_uid, other_p) in self.players.players.iter_mut() {
if p_uid == player_uid {
continue;
}
other_p.kill_tee(now, &mut self.tees, SpawnMode::ForceWeak);
}
}
}
pub(crate) fn pause(&mut self, player_uid: PlayerUid) {
let Some(p) = self.players.get_mut(player_uid) else {
return;
};
p.pause = match p.pause {
None => Some(PauseMode::Pause),
Some(PauseMode::Pause) => None,
}
}
pub(crate) fn move_all_to_keep_alive(
&mut self,
other: &mut Self,
from_entities: &mut EntityList,
mut to_entities: Option<&mut EntityList>,
clients: &mut SecondaryMap<PlayerUid, Client>,
) {
let player_uids: Vec<_> = self.players.players.keys().collect();
for player_uid in player_uids {
other.player_join_from_other_team(
player_uid,
self,
from_entities,
to_entities.as_deref_mut(),
);
clients.get_mut(player_uid).unwrap().activity = Activity::Playing(other.id);
}
}
pub(crate) fn move_all_to(
mut self,
other: &mut Self,
from_entities: &mut EntityList,
mut to_entities: Option<&mut EntityList>,
clients: &mut SecondaryMap<PlayerUid, Client>,
) {
let player_uids: Vec<_> = self.players.players.keys().collect();
for player_uid in player_uids {
other.player_join_from_other_team(
player_uid,
&mut self,
from_entities,
to_entities.as_deref_mut(),
);
clients
.get_mut(player_uid)
.expect("player must be in a team when saving")
.activity = Activity::Playing(other.id)
}
}
pub(crate) fn reset(&mut self, now: Instant) {
self.tee_order.reset();
self.tees.reset();
self.inner.tee_order.reset();
self.inner.tee_cores.reset();
for player in self.players.players.values_mut() {
player.tee = false;
player.previous_die_tick = player.die_tick;
player.die_tick = now;
}
self.map_entities = self.inner.map.entities.clone();
}
pub(crate) fn id(&self) -> TeamId {
self.id
}
}
impl EntityList {
pub(crate) fn reset(&mut self, team_id: TeamId) {
self.entities.retain(|(id, _)| *id != team_id)
}
}