use crate::ids::{PlayerUid, TeamId};
use crate::map::Map;
use crate::state::{AssociatedGameState, AssociatedGameStateMut, Bug, EntityList, TeamKind};
use crate::state::{Player, PrivilegedGameState};
use crate::tuning::Tuning;
use crate::{SnapOuter, ThHeader};
use std::collections::HashMap;
use rand::rngs::OsRng;
use rand::RngCore;
use rand_pcg::Lcg64Xsh32;
use slotmap::SecondaryMap;
use std::mem;
use std::rc::Rc;
use std::sync::{mpsc, Arc};
use twgame_core::console::Command;
use twgame_core::database::DatabaseWrite;
use twgame_core::database::{DatabaseResult, SaveTeam};
use twgame_core::net_msg::ClNetMessage;
use twgame_core::twsnap::time::{Duration, Instant};
use twgame_core::twsnap::{Snap, SnapId};
use twgame_core::{net_msg, Input};
use uuid::Uuid;
use vek::Vec2;
#[derive(Debug)]
pub(crate) enum Activity {
Playing(TeamId),
Spectator(Player),
}
#[derive(Debug)]
pub(crate) struct Client {
pub(crate) activity: Activity,
}
#[derive(Debug)]
struct Team {
entity_list: Option<EntityList>,
game_state: PrivilegedGameState,
}
impl AssociatedGameState for (&PrivilegedGameState, &HashMap<TeamId, Team>) {
fn get_game_state(&self, team_id: TeamId) -> &PrivilegedGameState {
if team_id.is_team0() {
self.0
} else {
let team = self.1.get(&team_id).unwrap();
assert!(team.entity_list.is_none());
&team.game_state
}
}
}
impl AssociatedGameState for (&mut PrivilegedGameState, &mut HashMap<TeamId, Team>) {
fn get_game_state(&self, team_id: TeamId) -> &PrivilegedGameState {
if team_id.is_team0() {
self.0
} else {
let team = self.1.get(&team_id).unwrap();
assert!(team.entity_list.is_none());
&team.game_state
}
}
}
impl AssociatedGameStateMut for (&mut PrivilegedGameState, &mut HashMap<TeamId, Team>) {
fn get_game_state_mut(&mut self, team_id: TeamId) -> &mut PrivilegedGameState {
if team_id.is_team0() {
self.0
} else {
let team = self.1.get_mut(&team_id).unwrap();
assert!(team.entity_list.is_none());
&mut team.game_state
}
}
}
#[derive(Debug)]
struct Teams {
team0_entity_list: EntityList,
team0_game_state: PrivilegedGameState,
teams: HashMap<TeamId, Team>,
compat: bool,
}
impl Teams {
fn new(
map: Rc<Arc<Map>>,
prng_description: Lcg64Xsh32,
sender: mpsc::Sender<DatabaseWrite>,
) -> Self {
let team0_game_state = PrivilegedGameState::new(
map,
TeamId::team0(),
TeamKind::Team0,
prng_description,
sender.clone(),
);
let team0_entity_list = EntityList::new(team0_game_state.entities_next_tick());
Self {
team0_entity_list,
team0_game_state,
teams: HashMap::new(),
compat: false,
}
}
}
#[derive(Debug)]
pub(crate) struct GameWorld {
cur_tick: Instant,
database_send: mpsc::Sender<DatabaseWrite>,
database_recv: mpsc::Receiver<DatabaseResult>,
uuid: Uuid,
clients: SecondaryMap<PlayerUid, Client>,
teams: Teams,
}
impl GameWorld {
pub(crate) fn new(
map: Rc<Arc<Map>>,
sender: mpsc::Sender<DatabaseWrite>,
receiver: mpsc::Receiver<DatabaseResult>,
) -> Self {
Self {
cur_tick: Instant::zero(),
database_send: sender.clone(),
database_recv: receiver,
uuid: Uuid::new_v4(),
clients: Default::default(),
teams: Teams::new(
map,
Lcg64Xsh32::new(OsRng.next_u64(), OsRng.next_u64()),
sender,
),
}
}
pub(crate) fn configure_with_teehistorian_parameters(&mut self, header: &ThHeader) {
self.uuid = header.game_uuid;
if let Some(prng_description) = header.prng_description.as_ref() {
let mut prng = prng_description.split(':');
assert_eq!(prng.next(), Some("pcg-xsh-rr"));
let state = u64::from_str_radix(prng.next().unwrap(), 16).unwrap();
let stream = u64::from_str_radix(prng.next().unwrap(), 16).unwrap();
self.teams
.team0_game_state
.overwrite_prng(Lcg64Xsh32::new(state, stream));
}
let mut tuning = Tuning::new();
for (name, value) in header.tuning.iter() {
let value: i32 = value.parse().unwrap();
assert!(tuning.apply_from_config(name, value as f32 / 100.0));
}
}
pub(crate) fn enable_ddnet_tele_compat_mode(&mut self, enabled: bool) {
self.teams.compat = enabled;
}
pub(crate) fn supply_prng_compat_data(&mut self, compat_data: &str) {
self.teams
.team0_game_state
.supply_prng_compat_data(compat_data);
}
pub(crate) fn enable_prng_compat_data_collection(&mut self) {
self.teams
.team0_game_state
.enable_prng_compat_data_collection();
}
pub(crate) fn retrieve_prng_compat_data(&mut self) -> Option<String> {
self.teams.team0_game_state.retrieve_prng_compat_data()
}
pub(crate) fn retrieve_bugs(&mut self) -> Vec<Bug> {
self.teams.team0_game_state.retrieve_bugs()
}
}
impl Teams {
pub(crate) fn player_join(&mut self, now: Instant, player_uid: PlayerUid) -> Activity {
self.team0_game_state.player_join(now, player_uid);
Activity::Playing(self.team0_game_state.id())
}
pub(crate) fn player_join_from_spectator(
&mut self,
player_uid: PlayerUid,
player: Player,
) -> Activity {
self.team0_game_state
.player_join_from_spectator(player_uid, player);
Activity::Playing(self.team0_game_state.id())
}
pub(crate) fn player_leave(
&mut self,
player_uid: PlayerUid,
client: &Client,
) -> Option<Player> {
self.get_player_team_mut(client)
.map(|(game_state, _)| game_state.player_leave(player_uid))
}
pub(crate) fn swap_tees(&mut self, id1: PlayerUid, id2: PlayerUid, tid: TeamId) {
let (game_state, entity_list) = if tid.is_team0() {
(&mut self.team0_game_state, &mut self.team0_entity_list)
} else {
let team = self.teams.get_mut(&tid).unwrap();
(
&mut team.game_state,
team.entity_list
.as_mut()
.unwrap_or(&mut self.team0_entity_list),
)
};
game_state.swap_tees(id1, id2);
entity_list.on_swap_tees(game_state, tid, id1, id2);
}
pub(crate) fn get_player_team(
&self,
client: &Client,
) -> Option<(&PrivilegedGameState, &EntityList)> {
match client.activity {
Activity::Playing(team) => {
if team.is_team0() {
Some((&self.team0_game_state, &self.team0_entity_list))
} else {
self.teams.get(&team).map(|team| {
(
&team.game_state,
team.entity_list.as_ref().unwrap_or(&self.team0_entity_list),
)
})
}
}
Activity::Spectator(_) => None,
}
}
pub(crate) fn get_player_team_mut(
&mut self,
client: &Client,
) -> Option<(&mut PrivilegedGameState, &mut EntityList)> {
match client.activity {
Activity::Playing(team) => {
if team.is_team0() {
Some((&mut self.team0_game_state, &mut self.team0_entity_list))
} else {
self.teams.get_mut(&team).map(|team| {
(
&mut team.game_state,
team.entity_list
.as_mut()
.unwrap_or(&mut self.team0_entity_list),
)
})
}
}
Activity::Spectator(_) => None,
}
}
fn create_team_if_not_exist(&mut self, team_id: TeamId) {
if team_id.is_team0() || self.teams.contains_key(&team_id) {
return;
}
let game_state = self.team0_game_state.create_new(team_id, self.compat);
let team = Team {
entity_list: (!self.compat).then(|| EntityList::new(game_state.entities_next_tick())),
game_state,
};
self.teams.insert(team_id, team);
}
pub(crate) fn player_team_change(
&mut self,
player_uid: PlayerUid,
from_team_id: &mut TeamId,
to_team_id: TeamId,
) {
if *from_team_id == to_team_id {
return;
}
self.create_team_if_not_exist(to_team_id);
if from_team_id.is_team0() {
let to_team = self.teams.get_mut(&to_team_id).unwrap();
to_team.game_state.player_join_from_other_team(
player_uid,
&mut self.team0_game_state,
&mut self.team0_entity_list,
to_team.entity_list.as_mut(),
);
} else if to_team_id.is_team0() {
let from_team = self.teams.get_mut(from_team_id).unwrap();
let (from_entity_list, to_entity_list) = match from_team.entity_list.as_mut() {
Some(entity_list) => (entity_list, Some(&mut self.team0_entity_list)),
None => (&mut self.team0_entity_list, None),
};
self.team0_game_state.player_join_from_other_team(
player_uid,
&mut from_team.game_state,
from_entity_list,
to_entity_list,
);
} else {
let [to_team, from_team] = self
.teams
.get_disjoint_mut([&to_team_id, from_team_id]);
let [to_team, from_team] = [to_team.unwrap(), from_team.unwrap()];
let (from_entity_list, to_entity_list) =
match (from_team.entity_list.as_mut(), to_team.entity_list.as_mut()) {
(Some(from_list), Some(to_list)) => (from_list, Some(to_list)),
(Some(from_list), None) => (from_list, Some(&mut self.team0_entity_list)),
(None, Some(to_list)) => (&mut self.team0_entity_list, Some(to_list)),
(None, None) => (&mut self.team0_entity_list, None),
};
to_team.game_state.player_join_from_other_team(
player_uid,
&mut from_team.game_state,
from_entity_list,
to_entity_list,
);
}
*from_team_id = to_team_id;
}
}
impl GameWorld {
pub(crate) fn player_join(&mut self, player_uid: PlayerUid) -> PlayerUid {
let activity = self.teams.player_join(self.cur_tick, player_uid);
self.clients.insert(player_uid, Client { activity });
player_uid
}
pub(crate) fn player_ready(&mut self, player_uid: PlayerUid) {
let client = self
.clients
.get(player_uid)
.expect("player_ready for non-existing client");
if let Some((game_state, _)) = self.teams.get_player_team_mut(client) {
game_state.player_ready(player_uid);
}
}
pub(crate) fn player_input(&mut self, player_uid: PlayerUid, input: &Input) {
let client = self
.clients
.get(player_uid)
.expect("player_input for non-existing client");
if let Some((game_state, _)) = self.teams.get_player_team_mut(client) {
game_state.player_input(player_uid, input);
}
}
pub(crate) fn player_leave(&mut self, player_uid: PlayerUid) {
let client = self
.clients
.remove(player_uid)
.expect("player_leave for non-existing client");
if let Some((game_state, _)) = self.teams.get_player_team_mut(&client) {
game_state.player_leave(player_uid);
}
}
pub(crate) fn player_team_change(&mut self, player_uid: PlayerUid, to_team_id: TeamId) {
let client = self
.clients
.get_mut(player_uid)
.expect("player_leave for non-existing client");
let Activity::Playing(ref mut from_team_id) = client.activity else {
return;
};
self.teams
.player_team_change(player_uid, from_team_id, to_team_id);
}
pub(crate) fn on_net_msg(&mut self, player_uid: PlayerUid, msg: &ClNetMessage) {
let cur_tick = self.cur_tick;
use ClNetMessage::*;
let client = self
.clients
.get_mut(player_uid)
.expect("on_net_msg for non-existing client");
match *msg {
ClSay(_) => {
}
ClSetTeam(ref spectator) => {
match spectator {
net_msg::Team::Spectators => {
if let Some(player) = self.teams.player_leave(player_uid, &*client) {
client.activity = Activity::Spectator(player);
}
}
net_msg::Team::Red | net_msg::Team::Blue => {
if matches!(client.activity, Activity::Spectator(_)) {
let mut tmp_activity = Activity::Playing(TeamId::team0());
mem::swap(&mut tmp_activity, &mut client.activity);
let Activity::Spectator(player) = tmp_activity else {
unreachable!("due to if-condition");
};
client.activity =
self.teams.player_join_from_spectator(player_uid, player);
}
}
}
}
ClSetSpectatorMode(_) => {
}
ClStartInfo(ref player_info) => {
if let Some((game_state, _)) = self.teams.get_player_team_mut(&*client) {
game_state.start_info(player_uid, player_info)
}
}
ClChangeInfo(ref player_info) => {
if let Some((game_state, _)) = self.teams.get_player_team_mut(&*client) {
game_state.change_info(player_uid, player_info)
}
}
ClKill => {
if let Some((game_state, _)) = self.teams.get_player_team_mut(&*client) {
game_state.kill(player_uid, cur_tick, false);
}
}
ClEmoticon(_) => {
}
ClVote(_) => {
}
ClCallVote(_) => {
}
ClIsDdnet(_) => {
}
ClShowOthers(_) => {
}
ClShowDistance(_) => {
}
ClCommand(_) => {}
ClCameraInfo(_) => {
}
ClEnableSpectatorCount(_) => {
}
}
}
pub(crate) fn on_command(&mut self, player_uid: PlayerUid, command: &Command) {
let Some(client) = self.clients.get(player_uid) else {
return;
};
let Some((game_state, entity_list)) = self.teams.get_player_team_mut(client) else {
return;
};
match *command {
Command::Team(team) => {
if (0..64).contains(&team) {
let team_id = TeamId::from_i32(team);
self.player_team_change(player_uid, team_id);
}
}
Command::ToggleLock => {
game_state.toggle_lock();
}
Command::SetLock(lock) => {
game_state.set_lock(lock);
}
Command::SaveEmpty | Command::Save(_) => {
if game_state.can_save() {
let save_string = game_state.save(self.cur_tick, self.uuid, true);
self.database_send
.send(DatabaseWrite::Save(
game_state.id().to_i32(),
SaveTeam::from_string(save_string),
))
.unwrap();
game_state.reset(self.cur_tick);
entity_list.reset(game_state.id());
}
}
Command::Load(_pw) => {
if game_state.can_load() {
self.database_send
.send(DatabaseWrite::Load(game_state.id().to_i32()))
.unwrap()
}
}
Command::Kill => {
game_state.kill(player_uid, self.cur_tick, true);
}
Command::Team0Mode => {
}
Command::Pause => game_state.pause(player_uid),
Command::Spec => game_state.pause(player_uid),
}
}
pub(crate) fn swap_tees(&mut self, id1: PlayerUid, id2: PlayerUid) {
if id1 == id2 {
return;
}
let Some(c1) = self.clients.get(id1) else {
return;
};
let Activity::Playing(t1_id) = c1.activity else {
return;
};
let Some(c2) = self.clients.get(id2) else {
return;
};
let Activity::Playing(t2_id) = c2.activity else {
return;
};
if t1_id != t2_id {
return;
}
self.teams.swap_tees(id1, id2, t1_id);
}
pub(crate) fn tick(&mut self, cur_time: Instant, snap_outer: &mut SnapOuter) {
if self.cur_tick.advance() != cur_time {
self.cur_tick = cur_time - Duration::T20MS;
}
assert_eq!(self.cur_tick.advance(), cur_time);
for game_state in self
.teams
.teams
.values_mut()
.map(|team| &mut team.game_state)
.chain([&mut self.teams.team0_game_state].into_iter())
{
game_state.clear_events();
}
for game_state in self
.teams
.teams
.values_mut()
.map(|team| &mut team.game_state)
.chain([&mut self.teams.team0_game_state].into_iter())
{
game_state.tick_0_fire_direct(self.cur_tick, snap_outer);
}
self.cur_tick = self.cur_tick.advance();
for game_state in self
.teams
.teams
.values_mut()
.map(|team| &mut team.game_state)
.chain([&mut self.teams.team0_game_state].into_iter())
{
game_state.tick_1_map_entities(self.cur_tick);
}
self.teams.team0_entity_list.tick_2_main_logic(
self.cur_tick,
&mut (&mut self.teams.team0_game_state, &mut self.teams.teams),
snap_outer,
);
for (_, team) in &mut self.teams.teams {
if let Some(entity_list) = team.entity_list.as_mut() {
entity_list.tick_2_main_logic(self.cur_tick, &mut team.game_state, snap_outer);
}
}
self.teams.teams.retain(|_, team| {
if team.game_state.tick_3_should_dissolve() {
let (from_entity_list, to_entity_list) = match team.entity_list.as_mut() {
Some(entity_list) => (entity_list, Some(&mut self.teams.team0_entity_list)),
None => (&mut self.teams.team0_entity_list, None),
};
team.game_state.move_all_to_keep_alive(
&mut self.teams.team0_game_state,
from_entity_list,
to_entity_list,
&mut self.clients,
);
false } else {
true
}
});
self.teams.team0_game_state.tick_4_move_tees(self.cur_tick);
for team in self.teams.teams.values_mut() {
team.game_state.tick_4_move_tees(self.cur_tick);
}
self.teams.team0_entity_list.tick_5_remove_destroyed(
&mut (&mut self.teams.team0_game_state, &mut self.teams.teams),
&mut self.clients,
None,
);
for team in self.teams.teams.values_mut() {
if let Some(entity_list) = team.entity_list.as_mut() {
entity_list.tick_5_remove_destroyed(
&mut team.game_state,
&mut self.clients,
Some(&mut self.teams.team0_game_state),
);
}
}
self.teams
.teams
.retain(|_, team| !team.game_state.tick_6_is_empty());
for database_result in self.database_recv.try_iter() {
match database_result {
DatabaseResult::LoadSuccess(team_id, load_str) => {
let team_id = TeamId::from_i32(team_id);
if let Some(team) = self.teams.teams.get_mut(&team_id) {
team.game_state.load(self.cur_tick, load_str);
}
}
DatabaseResult::LoadFailure(_) => {}
DatabaseResult::SaveSuccess(team_id) => {
let team_id = TeamId::from_i32(team_id);
if let Some(mut team) = self.teams.teams.remove(&team_id) {
let (from_entity_list, to_entity_list) = match team.entity_list.as_mut() {
Some(entity_list) => {
(entity_list, Some(&mut self.teams.team0_entity_list))
}
None => (&mut self.teams.team0_entity_list, None),
};
team.game_state.move_all_to(
&mut self.teams.team0_game_state,
from_entity_list,
to_entity_list,
&mut self.clients,
);
}
}
DatabaseResult::SaveFailure(_) => {
todo!("save last game state");
}
}
}
}
pub(crate) fn post_tick(&mut self) {
for team in self.teams.teams.values_mut() {
if let Some(entity_list) = team.entity_list.as_mut() {
team.game_state.post_tick(entity_list, self.cur_tick);
} else {
team.game_state
.post_tick(&mut self.teams.team0_entity_list, self.cur_tick);
}
}
self.teams
.team0_game_state
.post_tick(&mut self.teams.team0_entity_list, self.cur_tick)
}
pub(crate) fn snap(&self, snapshot: &mut Snap) {
use twgame_core::twsnap::flags::GameFlagsEx;
let game_info = snapshot.game_infos.get_mut_default(SnapId(0));
game_info.flags_ex = GameFlagsEx::TIMESCORE
| GameFlagsEx::GAMETYPE_RACE
| GameFlagsEx::GAMETYPE_DDRACE
| GameFlagsEx::GAMETYPE_DDNET
| GameFlagsEx::UNLIMITED_AMMO
| GameFlagsEx::RACE_RECORD_MESSAGE
| GameFlagsEx::ALLOW_EYE_WHEEL
| GameFlagsEx::ALLOW_HOOK_COLL
| GameFlagsEx::ALLOW_ZOOM
| GameFlagsEx::BUG_DDRACE_GHOST
| GameFlagsEx::BUG_DDRACE_INPUT
| GameFlagsEx::PREDICT_DDRACE
| GameFlagsEx::PREDICT_DDRACE_TILES
| GameFlagsEx::ENTITIES_DDNET
| GameFlagsEx::ENTITIES_DDRACE
| GameFlagsEx::ENTITIES_RACE
| GameFlagsEx::RACE
| GameFlagsEx::HUD_DDRACE;
self.teams.team0_entity_list.snap(
self.cur_tick,
&(&self.teams.team0_game_state, &self.teams.teams),
snapshot,
);
self.teams.team0_game_state.snap(TeamId::team0(), snapshot);
for (team_id, team) in &self.teams.teams {
if let Some(entity_list) = team.entity_list.as_ref() {
entity_list.snap(self.cur_tick, &team.game_state, snapshot);
}
team.game_state.snap(*team_id, snapshot);
}
}
pub(crate) fn tee_pos(&self, player_uid: PlayerUid) -> Option<Vec2<i32>> {
let client = self.clients.get(player_uid)?;
if let Some((game_state, _)) = self.teams.get_player_team(client) {
game_state.tee_pos(player_uid)
} else {
None
}
}
pub(crate) fn set_tee_pos(&mut self, player_uid: PlayerUid, pos: Option<Vec2<i32>>) {
let client = self
.clients
.get(player_uid)
.expect("set_tee_pos called for non-existing player");
let cur_tick = self.cur_tick;
if let Some((game_state, entity_list)) = self.teams.get_player_team_mut(client) {
game_state.set_tee_pos(entity_list, cur_tick, player_uid, pos);
} else {
self.on_net_msg(player_uid, &ClNetMessage::ClSetTeam(net_msg::Team::Red));
self.teams.team0_game_state.set_tee_pos(
&mut self.teams.team0_entity_list,
cur_tick,
player_uid,
pos,
);
}
}
pub(crate) fn player_team(&self, player_uid: PlayerUid) -> TeamId {
if let Some(client) = self.clients.get(player_uid) {
match client.activity {
Activity::Playing(tid) => tid,
Activity::Spectator(_) => TeamId::team0(),
}
} else {
panic!("player_team called for non-existing player (id {player_uid:?})");
}
}
pub(crate) fn is_empty(&self) -> bool {
let empty = self.teams.team0_entity_list.is_empty() && self.clients.len() == 0;
if self.teams.compat {
empty
} else {
empty
&& self
.teams
.teams
.iter()
.all(|team| team.1.entity_list.as_ref().unwrap().is_empty())
}
}
}