mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add basic client AI support
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2606,6 +2606,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"oj_polariton_auth",
|
"oj_polariton_auth",
|
||||||
"oj_rc_core",
|
"oj_rc_core",
|
||||||
|
"oj_rc_factory",
|
||||||
"polariton",
|
"polariton",
|
||||||
"polariton_server",
|
"polariton_server",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -32,5 +32,21 @@
|
|||||||
<setting name="Payment__note__notUsedHere">https://pay.rc.ngram.ca/</setting>
|
<setting name="Payment__note__notUsedHere">https://pay.rc.ngram.ca/</setting>
|
||||||
<setting name="Multiplayer__note__notUsedHere">mp.rc.ngram.ca:4542</setting>
|
<setting name="Multiplayer__note__notUsedHere">mp.rc.ngram.ca:4542</setting>
|
||||||
</ngram>
|
</ngram>
|
||||||
|
<mynewgroup>
|
||||||
|
<setting name="WebServicesServerAddress">ws.services.rc.ngram.ca:4532</setting>
|
||||||
|
<setting name="WebServicesServerAddress__note__forwardedTo">:4533</setting>
|
||||||
|
<setting name="SocialServerAddress">s.services.rc.ngram.ca:4534</setting>
|
||||||
|
<setting name="SocialServerAddress__note__forwardedTo">:4535</setting>
|
||||||
|
<setting name="ChatServerAddress">c.services.rc.ngram.ca:4536</setting>
|
||||||
|
<setting name="ChatServerAddress__note__forwardedTo">:4537</setting>
|
||||||
|
<setting name="SinglePlayerServerAddress">sp.services.rc.ngram.ca:4538</setting>
|
||||||
|
<setting name="SinglePlayerServerAddress__note__forwardedTo">:4539</setting>
|
||||||
|
<setting name="LobbyServerAddress">lobby.services.rc.ngram.ca:4540</setting>
|
||||||
|
<setting name="LobbyServerAddress__note__forwardedTo">:4541</setting>
|
||||||
|
<setting name="authUrl">https://live-auth.rc.ngram.ca/</setting>
|
||||||
|
<setting name="S3URL">https://static.rc.ngram.ca/live/data.json</setting>
|
||||||
|
<setting name="Payment__note__notUsedHere">https://pay.rc.ngram.ca/</setting>
|
||||||
|
<setting name="Multiplayer__note__notUsedHere">mp.rc.ngram.ca:4542</setting>
|
||||||
|
</mynewgroup>
|
||||||
</robocraft>
|
</robocraft>
|
||||||
</servers>
|
</servers>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use polariton::operation::Typed;
|
use polariton::operation::Typed;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayerData {
|
pub struct PlayerData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
|
|||||||
@@ -453,9 +453,8 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
|||||||
|
|
||||||
fn fake_players(&self) -> Vec<super::FakePlayer> {
|
fn fake_players(&self) -> Vec<super::FakePlayer> {
|
||||||
self.battle.multiplayer.fakes.iter().map(|player| super::FakePlayer {
|
self.battle.multiplayer.fakes.iter().map(|player| super::FakePlayer {
|
||||||
public_id: player.public_id.clone(),
|
|
||||||
display_name: player.display_name.clone(),
|
|
||||||
team: player.team,
|
team: player.team,
|
||||||
|
vehicle: player.vehicle.into_conf(),
|
||||||
implementation: player.implementation.clone().to_config(),
|
implementation: player.implementation.clone().to_config(),
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,15 +376,15 @@ pub struct LinksConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FakePlayer {
|
pub struct FakePlayer {
|
||||||
pub public_id: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub team: u8,
|
pub team: u8,
|
||||||
|
pub vehicle: VehicleInfo,
|
||||||
pub implementation: ClientEmulator,
|
pub implementation: ClientEmulator,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ClientEmulator {
|
pub enum ClientEmulator {
|
||||||
Experiment,
|
Experiment,
|
||||||
|
ClientAI,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ pub(super) fn default_net_conf() -> NetworkConf {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct FakePlayerConf {
|
pub struct FakePlayerConf {
|
||||||
pub public_id: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub team: u8,
|
pub team: u8,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
pub vehicle: super::garage::PrefabVehicle,
|
||||||
|
#[serde(flatten)]
|
||||||
pub implementation: ClientEmulation,
|
pub implementation: ClientEmulation,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,11 +69,41 @@ pub(super) fn default_fake_users() -> Vec<FakePlayerConf> {
|
|||||||
//Vec::default()
|
//Vec::default()
|
||||||
vec![
|
vec![
|
||||||
FakePlayerConf {
|
FakePlayerConf {
|
||||||
public_id: "ServerExperiment01".to_owned(),
|
|
||||||
display_name: "Server".to_owned(),
|
|
||||||
team: 1,
|
team: 1,
|
||||||
|
vehicle: super::garage::PrefabVehicle {
|
||||||
|
name: Some("fake0".to_owned()),
|
||||||
|
username: "Server0".to_owned(),
|
||||||
|
id: super::garage::PrefabId::Raw {
|
||||||
|
cube_data: Vec::from(crate::persist::VALID_ROBOT),
|
||||||
|
colour_data: Vec::from(crate::persist::VALID_COLOUR),
|
||||||
|
},
|
||||||
|
},
|
||||||
implementation: ClientEmulation::Experimental,
|
implementation: ClientEmulation::Experimental,
|
||||||
}
|
},
|
||||||
|
FakePlayerConf {
|
||||||
|
team: 0,
|
||||||
|
vehicle: super::garage::PrefabVehicle {
|
||||||
|
name: Some("fake1".to_owned()),
|
||||||
|
username: "Server1".to_owned(),
|
||||||
|
id: super::garage::PrefabId::Raw {
|
||||||
|
cube_data: Vec::from(crate::persist::VALID_ROBOT),
|
||||||
|
colour_data: Vec::from(crate::persist::VALID_COLOUR),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
implementation: ClientEmulation::ClientAI,
|
||||||
|
},
|
||||||
|
FakePlayerConf {
|
||||||
|
team: 1,
|
||||||
|
vehicle: super::garage::PrefabVehicle {
|
||||||
|
name: Some("fake2".to_owned()),
|
||||||
|
username: "Server2".to_owned(),
|
||||||
|
id: super::garage::PrefabId::Raw {
|
||||||
|
cube_data: Vec::from(crate::persist::VALID_ROBOT),
|
||||||
|
colour_data: Vec::from(crate::persist::VALID_COLOUR),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
implementation: ClientEmulation::ClientAI,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +111,14 @@ pub(super) fn default_fake_users() -> Vec<FakePlayerConf> {
|
|||||||
#[serde(tag = "impl")]
|
#[serde(tag = "impl")]
|
||||||
pub enum ClientEmulation {
|
pub enum ClientEmulation {
|
||||||
Experimental,
|
Experimental,
|
||||||
|
ClientAI,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientEmulation {
|
impl ClientEmulation {
|
||||||
pub(super) fn to_config(self) -> super::config::ClientEmulator {
|
pub(super) fn to_config(self) -> super::config::ClientEmulator {
|
||||||
match self {
|
match self {
|
||||||
Self::Experimental => super::config::ClientEmulator::Experiment,
|
Self::Experimental => super::config::ClientEmulator::Experiment,
|
||||||
|
Self::ClientAI => super::config::ClientEmulator::ClientAI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use argon2::PasswordVerifier;
|
use argon2::PasswordVerifier;
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
use crate::persist::config::ConfigProvider;
|
use crate::persist::config::ConfigProvider;
|
||||||
|
|
||||||
@@ -418,11 +419,22 @@ impl UserData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn resolve_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
|
pub(super) async fn resolve_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
|
||||||
|
let sha_bytes = sha2::Sha256::digest(vehicle.username.as_bytes());
|
||||||
|
let u32_bytes = [
|
||||||
|
sha_bytes[0],
|
||||||
|
sha_bytes[1],
|
||||||
|
sha_bytes[2],
|
||||||
|
sha_bytes[3],
|
||||||
|
];
|
||||||
|
let standard_uuid_uniqueness = (u32::from_be_bytes(u32_bytes) as i64) << 16; // middle 32 bits
|
||||||
match &vehicle.id {
|
match &vehicle.id {
|
||||||
crate::persist::config::VehicleDescriptor::Factory { factory: factory_id } => {
|
crate::persist::config::VehicleDescriptor::Factory { factory: factory_id } => {
|
||||||
match factory.vehicle(*factory_id).await {
|
match factory.vehicle(*factory_id).await {
|
||||||
Ok(Some(factory_vehicle)) => {
|
Ok(Some(factory_vehicle)) => {
|
||||||
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 30, *factory_id)));
|
let uuid_i64 = crate::persist::user::uuid_sanitize(
|
||||||
|
standard_uuid_uniqueness
|
||||||
|
^ crate::persist::user::i64_join((1 << 30, *factory_id))
|
||||||
|
);
|
||||||
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
||||||
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data));
|
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data));
|
||||||
let weapons_guess = vec![weapons_guess[0], 0, 0];
|
let weapons_guess = vec![weapons_guess[0], 0, 0];
|
||||||
@@ -470,7 +482,10 @@ impl UserData {
|
|||||||
} else {
|
} else {
|
||||||
db_vehicle.total_robot_cpu
|
db_vehicle.total_robot_cpu
|
||||||
};
|
};
|
||||||
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 31, *garage as u32)));
|
let uuid_i64 = crate::persist::user::uuid_sanitize(
|
||||||
|
standard_uuid_uniqueness
|
||||||
|
^ crate::persist::user::i64_join((1 << 31, *garage as u32))
|
||||||
|
);
|
||||||
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
||||||
Ok(super::ResolvedVehicle {
|
Ok(super::ResolvedVehicle {
|
||||||
mastery: 1,
|
mastery: 1,
|
||||||
@@ -506,7 +521,6 @@ impl UserData {
|
|||||||
cube_data,
|
cube_data,
|
||||||
colour_data,
|
colour_data,
|
||||||
} => {
|
} => {
|
||||||
use sha2::Digest;
|
|
||||||
let sha_bytes = sha2::Sha256::digest(cube_data);
|
let sha_bytes = sha2::Sha256::digest(cube_data);
|
||||||
let u32_bytes = [
|
let u32_bytes = [
|
||||||
sha_bytes[0],
|
sha_bytes[0],
|
||||||
@@ -514,7 +528,10 @@ impl UserData {
|
|||||||
sha_bytes[2],
|
sha_bytes[2],
|
||||||
sha_bytes[3],
|
sha_bytes[3],
|
||||||
];
|
];
|
||||||
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 29, u32::from_le_bytes(u32_bytes))));
|
let uuid_i64 = crate::persist::user::uuid_sanitize(
|
||||||
|
standard_uuid_uniqueness
|
||||||
|
^ crate::persist::user::i64_join((1 << 29, u32::from_le_bytes(u32_bytes)))
|
||||||
|
);
|
||||||
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
|
||||||
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&cube_data));
|
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&cube_data));
|
||||||
let weapons_guess = vec![
|
let weapons_guess = vec![
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
use super::account_json::UserData;
|
use super::account_json::UserData;
|
||||||
|
|
||||||
|
fn fake_impl_to_db(client_emu: &crate::persist::config::ClientEmulator) -> oj_rc_database::schema::multiplayer_game_player::ClientType {
|
||||||
|
match client_emu {
|
||||||
|
crate::persist::config::ClientEmulator::Experiment => oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental,
|
||||||
|
crate::persist::config::ClientEmulator::ClientAI => oj_rc_database::schema::multiplayer_game_player::ClientType::ClientAI,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl super::LobbyUser for UserData {
|
impl super::LobbyUser for UserData {
|
||||||
fn user_id(&self) -> i32 {
|
fn user_id(&self) -> i32 {
|
||||||
@@ -17,7 +24,7 @@ impl super::LobbyUser for UserData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_game(&self, game: super::GameDescriptor, players: Vec<super::PlayerLobbyDescriptor>, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
|
async fn start_game(&self, game: super::GameDescriptor, players: Vec<super::PlayerLobbyDescriptor>, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
|
||||||
let now = chrono::Utc::now().timestamp();
|
let now = chrono::Utc::now().timestamp();
|
||||||
let guid = crate::persist::user::str_to_i64(&game.guid)
|
let guid = crate::persist::user::str_to_i64(&game.guid)
|
||||||
.ok_or_else(|| polariton_server::operations::SimpleOpError::with_message(
|
.ok_or_else(|| polariton_server::operations::SimpleOpError::with_message(
|
||||||
@@ -32,7 +39,7 @@ impl super::LobbyUser for UserData {
|
|||||||
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
||||||
};
|
};
|
||||||
|
|
||||||
let fake_players = self.generate_fake_players_data(guid, cpu_counter, weapon_lister).await;
|
let fake_players = self.generate_fake_players_data(guid, factory, cpu_counter, weapon_lister).await?;
|
||||||
|
|
||||||
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
||||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
@@ -69,14 +76,15 @@ impl super::LobbyUser for UserData {
|
|||||||
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
||||||
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
||||||
|
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game_player::ClientType::Client),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.chain(fake_players.iter()
|
.chain(fake_players.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, fake)| {
|
.map(|(i, (fake, variant))| {
|
||||||
oj_rc_database::schema::multiplayer_game_player::ActiveModel {
|
oj_rc_database::schema::multiplayer_game_player::ActiveModel {
|
||||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(None),
|
user_id: oj_rc_database::sea_orm::ActiveValue::Set(None), // if ClientAI, they will be assigned to a user during game loading
|
||||||
game_id: oj_rc_database::sea_orm::ActiveValue::Set(game_dbo.id),
|
game_id: oj_rc_database::sea_orm::ActiveValue::Set(game_dbo.id),
|
||||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
player_id: oj_rc_database::sea_orm::ActiveValue::Set(((i + players_len) as u8) as _),
|
player_id: oj_rc_database::sea_orm::ActiveValue::Set(((i + players_len) as u8) as _),
|
||||||
@@ -85,6 +93,7 @@ impl super::LobbyUser for UserData {
|
|||||||
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(true),
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(true),
|
||||||
public_id: oj_rc_database::sea_orm::ActiveValue::Set(fake.name.clone()),
|
public_id: oj_rc_database::sea_orm::ActiveValue::Set(fake.name.clone()),
|
||||||
display_name: oj_rc_database::sea_orm::ActiveValue::Set(fake.display_name.clone()),
|
display_name: oj_rc_database::sea_orm::ActiveValue::Set(fake.display_name.clone()),
|
||||||
|
variant: oj_rc_database::sea_orm::ActiveValue::Set(fake_impl_to_db(variant)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
use super::account_json::UserData;
|
use super::account_json::UserData;
|
||||||
|
|
||||||
|
fn db_to_impl(client_emu: &oj_rc_database::schema::multiplayer_game_player::ClientType) -> Option<crate::persist::config::ClientEmulator> {
|
||||||
|
match client_emu {
|
||||||
|
oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental => Some(crate::persist::config::ClientEmulator::Experiment),
|
||||||
|
oj_rc_database::schema::multiplayer_game_player::ClientType::ClientAI => Some(crate::persist::config::ClientEmulator::ClientAI),
|
||||||
|
oj_rc_database::schema::multiplayer_game_player::ClientType::Client => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UserData {
|
impl UserData {
|
||||||
pub(super) async fn generate_fake_players_data(&self, _guid: i64, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Vec<crate::data::player_data::PlayerData> {
|
pub(super) async fn generate_fake_players_data(&self, _guid: i64, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
||||||
self.fake_players.iter()
|
let mut fakes = Vec::with_capacity(self.fake_players.len());
|
||||||
.map(|fake| crate::data::player_data::PlayerData {
|
for fake in self.fake_players.iter() {
|
||||||
name: fake.public_id.clone(),
|
let vehicle = self.resolve_vehicle(&fake.vehicle, factory, weapon_lister, cpu_counter).await?;
|
||||||
display_name: fake.display_name.clone(),
|
let out = (
|
||||||
mastery: 1,
|
crate::data::player_data::PlayerData {
|
||||||
tier: 1,
|
name: fake.vehicle.username.clone(),
|
||||||
robot_name: "fake".to_owned(),
|
display_name: fake.vehicle.username.clone(),
|
||||||
robot_map: crate::persist::VALID_ROBOT.into(),
|
mastery: vehicle.mastery,
|
||||||
group: None,
|
tier: vehicle.mastery,
|
||||||
team: fake.team as _,
|
robot_name: vehicle.robot_name,
|
||||||
has_premium: true,
|
robot_map: vehicle.robot_map,
|
||||||
robot_uuid: "1234_1234".to_owned(),
|
group: None,
|
||||||
cpu: cpu_counter.calculate_cpu(&mut std::io::Cursor::new(crate::persist::VALID_ROBOT)).total as _,
|
team: fake.team as _,
|
||||||
avatar_id: Some(0),
|
has_premium: true,
|
||||||
weapon_order: weapon_lister.guess_weapons(&mut std::io::Cursor::new(crate::persist::VALID_ROBOT)),
|
robot_uuid: vehicle.robot_uuid,
|
||||||
colour_map: crate::persist::VALID_COLOUR.into(),
|
cpu: vehicle.cpu,
|
||||||
is_ai: false,
|
avatar_id: Some(0),
|
||||||
spawn_effect: "Spawn".into(),
|
weapon_order: vehicle.weapon_order,
|
||||||
death_effect: "Explosion".into(),
|
colour_map: vehicle.colour_map,
|
||||||
player_rank: 1,
|
is_ai: fake.implementation == crate::persist::config::ClientEmulator::ClientAI,
|
||||||
weapon_rank: Default::default(),
|
spawn_effect: vehicle.spawn_effect,
|
||||||
})
|
death_effect: vehicle.death_effect,
|
||||||
.collect()
|
player_rank: 1,
|
||||||
|
weapon_rank: vehicle.weapon_rank,
|
||||||
|
},
|
||||||
|
fake.implementation
|
||||||
|
);
|
||||||
|
fakes.push(out);
|
||||||
|
}
|
||||||
|
Ok(fakes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +97,7 @@ impl super::MultiplayerUser for UserData {
|
|||||||
is_rewards_claimed: player.is_claimed,
|
is_rewards_claimed: player.is_claimed,
|
||||||
display_name: player.display_name,
|
display_name: player.display_name,
|
||||||
public_id: player.public_id,
|
public_id: player.public_id,
|
||||||
|
mode: db_to_impl(&player.variant),
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -247,11 +247,11 @@ impl SanctionType {
|
|||||||
pub trait LobbyUser {
|
pub trait LobbyUser {
|
||||||
fn user_id(&self) -> i32;
|
fn user_id(&self) -> i32;
|
||||||
async fn player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
|
async fn player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
|
||||||
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
|
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FakePlayers {
|
pub struct FakePlayers {
|
||||||
pub players: Vec<crate::data::player_data::PlayerData>
|
pub players: Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CurrentGameEvent {
|
pub struct CurrentGameEvent {
|
||||||
@@ -291,6 +291,7 @@ pub struct PlayerDescriptor {
|
|||||||
pub public_id: String,
|
pub public_id: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub is_rewards_claimed: bool,
|
pub is_rewards_claimed: bool,
|
||||||
|
pub mode: Option<crate::persist::config::ClientEmulator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250918_000001_add_player_variant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Add player variant columns
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(crate::schema::multiplayer_game_player::Entity)
|
||||||
|
.add_column(ColumnDef::new(crate::schema::multiplayer_game_player::Column::Variant).string().not_null().default(crate::schema::multiplayer_game_player::ClientType::Client))
|
||||||
|
.to_owned()
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the added column
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(crate::schema::multiplayer_game_player::Entity)
|
||||||
|
.drop_column(crate::schema::multiplayer_game_player::Column::Variant)
|
||||||
|
.to_owned()
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ mod m20250713_000001_create_game_table;
|
|||||||
mod m20250713_000002_create_player_table;
|
mod m20250713_000002_create_player_table;
|
||||||
mod m20250722_000001_create_game_event_table;
|
mod m20250722_000001_create_game_event_table;
|
||||||
mod m20250816_000001_add_fake_players;
|
mod m20250816_000001_add_fake_players;
|
||||||
|
mod m20250918_000001_add_player_variant;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20250713_000002_create_player_table::Migration),
|
Box::new(m20250713_000002_create_player_table::Migration),
|
||||||
Box::new(m20250722_000001_create_game_event_table::Migration),
|
Box::new(m20250722_000001_create_game_event_table::Migration),
|
||||||
Box::new(m20250816_000001_add_fake_players::Migration),
|
Box::new(m20250816_000001_add_fake_players::Migration),
|
||||||
|
Box::new(m20250918_000001_add_player_variant::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub struct Model {
|
|||||||
pub is_claimed: bool,
|
pub is_claimed: bool,
|
||||||
pub public_id: String,
|
pub public_id: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
|
pub variant: ClientType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
@@ -45,3 +46,11 @@ impl Related<super::user::Entity> for Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||||
|
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")]
|
||||||
|
pub enum ClientType {
|
||||||
|
Client,
|
||||||
|
ClientAI,
|
||||||
|
ServerExperimental,
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ polariton.workspace = true
|
|||||||
oj_polariton_auth = { version = "*", path = "../polariton_auth" }
|
oj_polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||||
polariton_server.workspace = true
|
polariton_server.workspace = true
|
||||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||||
|
oj_rc_factory = { version = "*", path = "../rc_factory" }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
|||||||
@@ -38,13 +38,14 @@ pub struct QueueHandler {
|
|||||||
hostname: String,
|
hostname: String,
|
||||||
hostport: u16,
|
hostport: u16,
|
||||||
network_conf: crate::data::network::NetworkConfigData,
|
network_conf: crate::data::network::NetworkConfigData,
|
||||||
|
factory: std::sync::Arc<oj_rc_core::factory::Factory>,
|
||||||
cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>,
|
cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>,
|
||||||
weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,
|
weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,
|
||||||
change_strategy: GamemodeChangeStrategy,
|
change_strategy: GamemodeChangeStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueueHandler {
|
impl QueueHandler {
|
||||||
pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>, weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,) -> Self {
|
pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, factory: std::sync::Arc<oj_rc_core::factory::Factory>, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>, weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,) -> Self {
|
||||||
let (domain, port_str) = game_host.split_once(':').expect("Invalid redirect address (must be domain:port)");
|
let (domain, port_str) = game_host.split_once(':').expect("Invalid redirect address (must be domain:port)");
|
||||||
Self {
|
Self {
|
||||||
users_in_queue: tokio::sync::Mutex::new(HashMap::new()),
|
users_in_queue: tokio::sync::Mutex::new(HashMap::new()),
|
||||||
@@ -53,6 +54,7 @@ impl QueueHandler {
|
|||||||
hostname: domain.to_owned(),
|
hostname: domain.to_owned(),
|
||||||
hostport: port_str.parse().expect("Invalid redirect port"),
|
hostport: port_str.parse().expect("Invalid redirect port"),
|
||||||
network_conf: crate::data::network::NetworkConfigData::from_conf(oj_rc_core::ConfigProvider::<()>::network_config(conf)),
|
network_conf: crate::data::network::NetworkConfigData::from_conf(oj_rc_core::ConfigProvider::<()>::network_config(conf)),
|
||||||
|
factory,
|
||||||
cpu_counter,
|
cpu_counter,
|
||||||
weapon_guesser,
|
weapon_guesser,
|
||||||
change_strategy: GamemodeChangeStrategy::from_core(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(conf).queue_mode),
|
change_strategy: GamemodeChangeStrategy::from_core(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(conf).queue_mode),
|
||||||
@@ -90,9 +92,11 @@ impl QueueHandler {
|
|||||||
is_custom: false,
|
is_custom: false,
|
||||||
is_complete: false,
|
is_complete: false,
|
||||||
};
|
};
|
||||||
match user.start_game(game_desc, player_descs, &self.cpu_counter, &self.weapon_guesser).await {
|
match user.start_game(game_desc, player_descs, self.factory.as_ref(), &self.cpu_counter, &self.weapon_guesser).await {
|
||||||
Ok(fakes) => {
|
Ok(fakes) => {
|
||||||
let player_datas = players.iter().map(|x| x.player.clone()).chain(fakes.players.into_iter()).collect();
|
let player_datas = players.iter().map(|x| x.player.clone())
|
||||||
|
.chain(fakes.players.into_iter().map(|(desc, _emu)| desc))
|
||||||
|
.collect();
|
||||||
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
||||||
host: self.hostname.clone(),
|
host: self.hostname.clone(),
|
||||||
port: self.hostport,
|
port: self.hostport,
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
|
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
|
||||||
|
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config"));
|
||||||
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||||
let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, parsers.cpu_counter(), parsers.weapon_order()));
|
let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, factory.clone(), parsers.cpu_counter(), parsers.weapon_order()));
|
||||||
|
|
||||||
let init_ctx = InitConfig {
|
let init_ctx = InitConfig {
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ pub struct GameMatches {
|
|||||||
routing: std::collections::HashMap<i32, String>, // user id to game guid
|
routing: std::collections::HashMap<i32, String>, // user id to game guid
|
||||||
mode_configs: oj_rc_core::data::game_mode::GameModeConfigs,
|
mode_configs: oj_rc_core::data::game_mode::GameModeConfigs,
|
||||||
map_configs: std::collections::HashMap<String, oj_rc_core::persist::config::MapConfig>,
|
map_configs: std::collections::HashMap<String, oj_rc_core::persist::config::MapConfig>,
|
||||||
fake_players: Vec<oj_rc_core::persist::config::FakePlayer>,
|
//fake_players: Vec<oj_rc_core::persist::config::FakePlayer>,
|
||||||
cube_parsers: std::sync::Arc<oj_rc_core::cubes::CubeParsers>,
|
cube_parsers: std::sync::Arc<oj_rc_core::cubes::CubeParsers>,
|
||||||
ba_settings: std::sync::Arc<oj_rc_core::persist::config::BattleArenaResolver>,
|
ba_settings: std::sync::Arc<oj_rc_core::persist::config::BattleArenaResolver>,
|
||||||
pit_settings: std::sync::Arc<oj_rc_core::persist::config::PitSettings>,
|
pit_settings: std::sync::Arc<oj_rc_core::persist::config::PitSettings>,
|
||||||
@@ -21,7 +21,7 @@ impl GameMatches {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(map, conf)| (oj_rc_core::data::game_mode::GameMap::from_persist(map).as_str().to_owned(), conf))
|
.map(|(map, conf)| (oj_rc_core::data::game_mode::GameMap::from_persist(map).as_str().to_owned(), conf))
|
||||||
.collect(),
|
.collect(),
|
||||||
fake_players: <oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::fake_players(conf),
|
//fake_players: <oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::fake_players(conf),
|
||||||
cube_parsers,
|
cube_parsers,
|
||||||
ba_settings: std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::ba_settings(conf)),
|
ba_settings: std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::ba_settings(conf)),
|
||||||
pit_settings: std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::pit_settings(conf)),
|
pit_settings: std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::pit_settings(conf)),
|
||||||
@@ -36,21 +36,21 @@ impl GameMatches {
|
|||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_player_emulator(&self, emu: oj_rc_core::persist::config::ClientEmulator) -> Box<dyn super::fake::FakeUser> {
|
fn build_player_emulator(&self, emu: oj_rc_core::persist::config::ClientEmulator, player: &oj_rc_core::persist::user::PlayerDescriptor) -> Box<dyn super::fake::FakeUser> {
|
||||||
|
let owned_player = player.to_owned();
|
||||||
match emu {
|
match emu {
|
||||||
oj_rc_core::persist::config::ClientEmulator::Experiment => Box::new(super::fake::ExperimentalPlayer::new()),
|
oj_rc_core::persist::config::ClientEmulator::Experiment => Box::new(super::fake::ExperimentalPlayer::new(owned_player)),
|
||||||
|
oj_rc_core::persist::config::ClientEmulator::ClientAI => Box::new(super::fake::ClientAIPlayer::new(owned_player)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_fake_players(&self, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> std::collections::HashMap<u8, Box<dyn super::fake::FakeUser>> {
|
fn build_fake_players(&self, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> std::collections::HashMap<u8, Box<dyn super::fake::FakeUser>> {
|
||||||
let mut fake_player_i = 0;
|
let mut fakes = std::collections::HashMap::new();
|
||||||
let mut fakes = std::collections::HashMap::with_capacity(self.fake_players.len());
|
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
if fake_player_i >= self.fake_players.len() { break; }
|
//if player.user_id.is_some() { continue; }
|
||||||
if player.user_id.is_none() {
|
if let Some(emu_mode) = player.mode {
|
||||||
let fake = self.build_player_emulator(self.fake_players[fake_player_i].implementation);
|
let fake = self.build_player_emulator(emu_mode, player);
|
||||||
fakes.insert(player.player_id, fake);
|
fakes.insert(player.player_id, fake);
|
||||||
fake_player_i += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fakes
|
fakes
|
||||||
@@ -81,12 +81,14 @@ impl GameMatches {
|
|||||||
let players = user.game_players(guid).await?;
|
let players = user.game_players(guid).await?;
|
||||||
if players.is_empty() {
|
if players.is_empty() {
|
||||||
log::warn!("No players found for game {}, loading may not work correctly", guid);
|
log::warn!("No players found for game {}, loading may not work correctly", guid);
|
||||||
|
} else {
|
||||||
|
log::info!("There are {} ({} real) players for game {}", players.len(), players.iter().filter(|x| x.user_id.is_some()).count(), guid);
|
||||||
}
|
}
|
||||||
let fakes = self.build_fake_players(&players);
|
let fakes = self.build_fake_players(&players);
|
||||||
let fakes_handler = super::fake::Handler::start(fakes, players.clone()).await;
|
let fakes_handler = super::fake::Handler::start(fakes, players.clone()).await;
|
||||||
match game_info.mode {
|
match game_info.mode {
|
||||||
oj_rc_core::data::game_mode::GameMode::SuddenDeath => {
|
oj_rc_core::data::game_mode::GameMode::SuddenDeath => {
|
||||||
let inner = super::modes::EliminationLogic::new(&self.mode_configs.elimination, &map_config);
|
let inner = super::modes::EliminationLogic::new(&self.mode_configs.elimination, &map_config, &players);
|
||||||
let engine = super::GenericGamemodeEngine::new(
|
let engine = super::GenericGamemodeEngine::new(
|
||||||
game_info,
|
game_info,
|
||||||
map_config,
|
map_config,
|
||||||
@@ -108,7 +110,7 @@ impl GameMatches {
|
|||||||
code: oj_rc_core::persist::user::MultiplayerErrorCode::CustomString,
|
code: oj_rc_core::persist::user::MultiplayerErrorCode::CustomString,
|
||||||
message: e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to resolve special settings for Battle Arena".to_owned()),
|
message: e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to resolve special settings for Battle Arena".to_owned()),
|
||||||
})?;
|
})?;
|
||||||
let inner = super::modes::BattleArenaLogic::new(&self.mode_configs.battle_arena, &map_config, &self.cube_parsers, resolved_ba_conf);
|
let inner = super::modes::BattleArenaLogic::new(&self.mode_configs.battle_arena, &map_config, &self.cube_parsers, &players, resolved_ba_conf);
|
||||||
let engine = super::GenericGamemodeEngine::new(
|
let engine = super::GenericGamemodeEngine::new(
|
||||||
game_info,
|
game_info,
|
||||||
map_config,
|
map_config,
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ pub struct RlnlPacket {
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
||||||
/// Called when player joins the game server (after authentication).
|
/// Called when player joins the game server (after authentication).
|
||||||
async fn on_player_join(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool;
|
async fn on_player_join(&self, generic: &super::GenericGamemodeEngine<Self>, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool;
|
||||||
/// Called when player leaves the game server (i.e. player disconnects).
|
/// Called when player leaves the game server (i.e. player disconnects).
|
||||||
async fn on_player_end(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool;
|
async fn on_player_end(&self, generic: &super::GenericGamemodeEngine<Self>, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool;
|
||||||
/// Called when a player's vehicle is destroyed by another player.
|
/// Called when a player's vehicle is destroyed by another player.
|
||||||
async fn on_vehicle_destroyed(&self, generic: &super::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool;
|
async fn on_vehicle_destroyed(&self, generic: &super::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool;
|
||||||
/// Called when a player's vehicle self-destructs.
|
/// Called when a player's vehicle self-destructs.
|
||||||
@@ -21,7 +21,7 @@ pub trait CustomGameLogic: Sized + Send + Sync + 'static {
|
|||||||
/// Called during loading, before the sync stage is entered.
|
/// Called during loading, before the sync stage is entered.
|
||||||
///
|
///
|
||||||
/// Roughly, loading is as follows: `Loading -> WaitingForSync -> Sync -> WaitingToStart -> InGame`.
|
/// Roughly, loading is as follows: `Loading -> WaitingForSync -> Sync -> WaitingToStart -> InGame`.
|
||||||
async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> Vec<RlnlPacket>;
|
async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine<Self>, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> Vec<RlnlPacket>;
|
||||||
/// Called when loading completes and the countdown is starting
|
/// Called when loading completes and the countdown is starting
|
||||||
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool;
|
||||||
/// Called when the game is marked as complete
|
/// Called when the game is marked as complete
|
||||||
|
|||||||
70
rc_multiplayer/src/matches/fake/client_ai.rs
Normal file
70
rc_multiplayer/src/matches/fake/client_ai.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
pub struct ClientAIPlayer {
|
||||||
|
me: oj_rc_core::persist::user::PlayerDescriptor,
|
||||||
|
is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||||
|
assigned_to: std::sync::atomic::AtomicU16, // player_id but where u16::MAX means None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientAIPlayer {
|
||||||
|
pub fn new(me: oj_rc_core::persist::user::PlayerDescriptor) -> Self {
|
||||||
|
Self {
|
||||||
|
me,
|
||||||
|
is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||||
|
assigned_to: std::sync::atomic::AtomicU16::new(u16::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assigned_to_player_id(&self) -> Option<u8> {
|
||||||
|
let id = self.assigned_to.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if id > u8::MAX as u16 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(id as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_assigned_to(&self, player_id: Option<u8>) {
|
||||||
|
self.assigned_to.store(player_id.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::FakeUser for ClientAIPlayer {
|
||||||
|
async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8) {
|
||||||
|
let first_fake_i = descriptors.iter()
|
||||||
|
.filter(|x| x.team == self.me.team)
|
||||||
|
.enumerate()
|
||||||
|
.find(|x| x.1.mode != None)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap();
|
||||||
|
let my_i = descriptors.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|x| x.1.player_id == player_id)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap();
|
||||||
|
let real_teammates_count = descriptors.iter()
|
||||||
|
.filter(|x| x.team == self.me.team && x.mode.is_none())
|
||||||
|
.count();
|
||||||
|
let my_offset = (my_i - first_fake_i) % real_teammates_count;
|
||||||
|
for (i, teammate) in descriptors.iter().filter(|x| x.team == self.me.team && x.mode.is_none()).enumerate() {
|
||||||
|
if i >= my_offset {
|
||||||
|
self.set_assigned_to(Some(teammate.player_id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.assigned_to_player_id().is_none() {
|
||||||
|
log::warn!("Failed to assign client AI player {} to a real client; offset:{}, first_fake:{}, reals:{}, me:{}", player_id, my_offset, first_fake_i, real_teammates_count, my_i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_ready(&self, _real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_end(&self) {
|
||||||
|
self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn running_on(&self) -> Option<u8> {
|
||||||
|
self.assigned_to_player_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,14 @@ use rand::Rng;
|
|||||||
use byteserde::ser_heap::ByteSerializeHeap;
|
use byteserde::ser_heap::ByteSerializeHeap;
|
||||||
|
|
||||||
pub struct ExperimentalPlayer {
|
pub struct ExperimentalPlayer {
|
||||||
me: tokio::sync::RwLock<Option<oj_rc_core::persist::user::PlayerDescriptor>>,
|
me: oj_rc_core::persist::user::PlayerDescriptor,
|
||||||
is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExperimentalPlayer {
|
impl ExperimentalPlayer {
|
||||||
pub fn new() -> Self {
|
pub fn new(me: oj_rc_core::persist::user::PlayerDescriptor) -> Self {
|
||||||
Self {
|
Self {
|
||||||
me: tokio::sync::RwLock::new(None),
|
me,
|
||||||
is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,16 +17,14 @@ impl ExperimentalPlayer {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl super::FakeUser for ExperimentalPlayer {
|
impl super::FakeUser for ExperimentalPlayer {
|
||||||
async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8) {
|
async fn on_init(&self, _descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], _player_id: u8) {
|
||||||
if let Some(my_desc) = descriptors.iter().find(|x| x.player_id == player_id) {
|
|
||||||
*self.me.write().await = Some(my_desc.to_owned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||||
let movement_rx = real_players.values().map(|x| x.to_owned()).collect();
|
let movement_rx = real_players.values().map(|x| x.to_owned()).collect();
|
||||||
let is_complete = self.is_complete.clone();
|
let is_complete = self.is_complete.clone();
|
||||||
let player_id = self.me.read().await.as_ref().unwrap().player_id;
|
let player_id = self.me.player_id;
|
||||||
tokio::task::spawn(erratic_behaviour(movement_rx, is_complete, player_id));
|
tokio::task::spawn(erratic_behaviour(movement_rx, is_complete, player_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ enum Message {
|
|||||||
Ready {
|
Ready {
|
||||||
real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>,
|
real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>,
|
||||||
},
|
},
|
||||||
|
ClientMap {
|
||||||
|
responder: tokio::sync::oneshot::Sender<std::collections::HashMap<u8, Vec<u8>>>, // real player_id -> client AIs
|
||||||
|
},
|
||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,6 +25,13 @@ impl Handler {
|
|||||||
Self::log_failure(self.tx.send(Message::Ready { real_players }));
|
Self::log_failure(self.tx.send(Message::Ready { real_players }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// real player_id -> list of non-user player_id
|
||||||
|
pub async fn get_client_ais(&self) -> std::collections::HashMap<u8, Vec<u8>> {
|
||||||
|
let (responder, rx) = tokio::sync::oneshot::channel();
|
||||||
|
Self::log_failure(self.tx.send(Message::ClientMap { responder }));
|
||||||
|
rx.await.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&self) {
|
pub fn stop(&self) {
|
||||||
Self::log_failure(self.tx.send(Message::Stop));
|
Self::log_failure(self.tx.send(Message::Stop));
|
||||||
}
|
}
|
||||||
@@ -52,6 +62,21 @@ async fn handler_loop(mut rx: tokio::sync::mpsc::UnboundedReceiver<Message>, pla
|
|||||||
fake.on_ready(&real_players).await;
|
fake.on_ready(&real_players).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Message::ClientMap { responder } => {
|
||||||
|
let mut map = std::collections::HashMap::<u8, Vec<u8>>::new();
|
||||||
|
for (player_id, fake) in players.iter() {
|
||||||
|
if let Some(running_on) = fake.running_on().await {
|
||||||
|
if let Some(fakes_on) = map.get_mut(&running_on) {
|
||||||
|
fakes_on.push(*player_id);
|
||||||
|
} else {
|
||||||
|
map.insert(running_on, vec![*player_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(_e) = responder.send(map) {
|
||||||
|
log::warn!("Failed to send response for client AI map message");
|
||||||
|
}
|
||||||
|
},
|
||||||
Message::Stop => {
|
Message::Stop => {
|
||||||
for fake in players.values() {
|
for fake in players.values() {
|
||||||
fake.on_end().await;
|
fake.on_end().await;
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ pub use traits::FakeUser;
|
|||||||
|
|
||||||
mod experimental;
|
mod experimental;
|
||||||
pub use experimental::ExperimentalPlayer;
|
pub use experimental::ExperimentalPlayer;
|
||||||
|
|
||||||
|
mod client_ai;
|
||||||
|
pub use client_ai::ClientAIPlayer;
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait FakeUser: Send + Sync {
|
pub trait FakeUser: Send + Sync {
|
||||||
async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8);
|
async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8);
|
||||||
|
|
||||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>);
|
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>);
|
||||||
|
|
||||||
//fn on_damage(&self, data: &rlnl::events::ingame::DestroyCubesFull);
|
//fn on_damage(&self, data: &rlnl::events::ingame::DestroyCubesFull);
|
||||||
|
|
||||||
async fn on_end(&self);
|
async fn on_end(&self);
|
||||||
|
|
||||||
|
/// player id which is running the fake user (client AI only)
|
||||||
|
async fn running_on(&self) -> Option<u8> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
pub(super) struct UserConnection {
|
pub(super) struct UserConnection {
|
||||||
pub(super) user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::MultiplayerUser + Send + Sync + 'static>>,
|
pub(super) user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::MultiplayerUser + Send + Sync + 'static>>,
|
||||||
pub(super) connection: UserSender,
|
pub(super) connection: UserSender,
|
||||||
|
aliases: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct UserDescriptor {
|
||||||
pub(super) state: std::sync::Arc<UserState>,
|
pub(super) state: std::sync::Arc<UserState>,
|
||||||
pub(super) machine: MachineState,
|
pub(super) machine: MachineState,
|
||||||
pub(super) descriptor: oj_rc_core::persist::user::PlayerDescriptor,
|
pub(super) descriptor: std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>,
|
||||||
pub(super) counters: UserData,
|
pub(super) counters: UserData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
impl UserDescriptor {
|
||||||
|
fn new(descriptor: oj_rc_core::persist::user::PlayerDescriptor) -> Self {
|
||||||
|
Self {
|
||||||
|
state: std::sync::Arc::new(UserState::new()),
|
||||||
|
machine: MachineState::new(),
|
||||||
|
descriptor: std::sync::Arc::new(descriptor),
|
||||||
|
counters: UserData::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#[allow(dead_code)]
|
||||||
pub(super) struct FakeUser {
|
pub(super) struct FakeUser {
|
||||||
pub(super) state: std::sync::Arc<UserState>,
|
pub(super) state: std::sync::Arc<UserState>,
|
||||||
pub(super) machine: MachineState,
|
pub(super) machine: MachineState,
|
||||||
@@ -27,7 +42,7 @@ impl FakeUser {
|
|||||||
counters: UserData::new(),
|
counters: UserData::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(super) struct UserSender {
|
pub(super) struct UserSender {
|
||||||
@@ -188,8 +203,9 @@ impl ConnectionMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
|
pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
|
||||||
pub users: tokio::sync::RwLock<std::collections::HashMap<u8, UserConnection>>,
|
pub users: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::Arc<UserConnection>>>,
|
||||||
pub user_id_map: tokio::sync::RwLock<std::collections::HashMap<i32, u8>>,
|
descriptors: std::collections::HashMap<u8, UserDescriptor>,
|
||||||
|
user_id_map: std::collections::HashMap<i32, u8>,
|
||||||
//pub recv: tokio::sync::Mutex<tokio::sync::mpsc::Receiver<super::GameMessage>>,
|
//pub recv: tokio::sync::Mutex<tokio::sync::mpsc::Receiver<super::GameMessage>>,
|
||||||
//pub send: tokio::sync::mpsc::Sender<super::GameMessage>,
|
//pub send: tokio::sync::mpsc::Sender<super::GameMessage>,
|
||||||
//pub game_guid: String,
|
//pub game_guid: String,
|
||||||
@@ -198,9 +214,9 @@ pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
|
|||||||
pub map_config: std::sync::Arc<oj_rc_core::persist::config::MapConfig>,
|
pub map_config: std::sync::Arc<oj_rc_core::persist::config::MapConfig>,
|
||||||
pub game_descriptor: oj_rc_core::persist::user::GameDescriptor,
|
pub game_descriptor: oj_rc_core::persist::user::GameDescriptor,
|
||||||
pub game_duration: std::time::Duration,
|
pub game_duration: std::time::Duration,
|
||||||
pub players_info: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>,
|
//pub players_info: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>,
|
||||||
pub custom_logic_handler: L,
|
pub custom_logic_handler: L,
|
||||||
pub fake_users: std::collections::HashMap<u8, FakeUser>,
|
//pub fake_users: std::collections::HashMap<u8, FakeUser>,
|
||||||
pub fakes_handler: super::fake::Handler,
|
pub fakes_handler: super::fake::Handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,21 +233,28 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
fakes_handler: super::fake::Handler,
|
fakes_handler: super::fake::Handler,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
||||||
let fake_users = players.iter()
|
/*let fake_users = players.iter()
|
||||||
.filter(|player| player.user_id.is_none())
|
.filter(|player| player.user_id.is_none())
|
||||||
.map(|player| (player.team as u8, FakeUser::new(player.to_owned())))
|
.map(|player| (player.team as u8, FakeUser::new(player.to_owned())))
|
||||||
|
.collect();*/
|
||||||
|
|
||||||
|
let descriptors = players.iter()
|
||||||
|
.map(|player| (player.player_id as u8, UserDescriptor::new(player.to_owned())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let user_id_map = players.iter()
|
||||||
|
.filter_map(|player| player.user_id.map(|id| (id, player.team as u8)))
|
||||||
.collect();
|
.collect();
|
||||||
Self {
|
Self {
|
||||||
users: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
users: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
||||||
user_id_map: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
descriptors,
|
||||||
|
user_id_map,
|
||||||
is_complete: std::sync::atomic::AtomicBool::new(false),
|
is_complete: std::sync::atomic::AtomicBool::new(false),
|
||||||
game_start: std::sync::atomic::AtomicI64::new(i64::MIN),
|
game_start: std::sync::atomic::AtomicI64::new(i64::MIN),
|
||||||
map_config: std::sync::Arc::new(map),
|
map_config: std::sync::Arc::new(map),
|
||||||
game_descriptor: game,
|
game_descriptor: game,
|
||||||
game_duration: std::time::Duration::from_secs((config.game_time_minutes as u64) * 60),
|
game_duration: std::time::Duration::from_secs((config.game_time_minutes as u64) * 60),
|
||||||
players_info: std::sync::Arc::new(players),
|
|
||||||
custom_logic_handler: custom,
|
custom_logic_handler: custom,
|
||||||
fake_users,
|
|
||||||
fakes_handler,
|
fakes_handler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,15 +264,27 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
&self.game_descriptor.guid
|
&self.game_descriptor.guid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn user_key_by_user_id(&self, user_id: i32) -> Option<u8> {
|
pub(super) fn user_key_by_user_id(&self, user_id: i32) -> Option<u8> {
|
||||||
self.user_id_map.read().await.get(&user_id).copied()
|
self.user_id_map.get(&user_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn user_descriptor(&self, player_id: u8) -> Option<&'_ UserDescriptor> {
|
||||||
|
self.descriptors.get(&player_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn user_descriptors(&self) -> &'_ std::collections::HashMap<u8, UserDescriptor> {
|
||||||
|
&self.descriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
/*pub(super) async fn user_connection(&self, player_id: u8) -> Option<&'_ UserConnection> {
|
||||||
|
self.users.read().await.get(&player_id)
|
||||||
|
}*/
|
||||||
|
|
||||||
pub(super) async fn rebroadcast<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
|
pub(super) async fn rebroadcast<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
if user_id == conn.user.user_id() { continue; }
|
if user_id == conn.user.user_id() { continue; }
|
||||||
if in_game {
|
if in_game {
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
||||||
}
|
}
|
||||||
let sender = crate::handlers::RlnlSender::new(&conn.connection.sender);
|
let sender = crate::handlers::RlnlSender::new(&conn.connection.sender);
|
||||||
@@ -263,10 +298,10 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn rebroadcast_dataless(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
|
pub(super) async fn rebroadcast_dataless(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
if user_id == conn.user.user_id() { continue; }
|
if user_id == conn.user.user_id() { continue; }
|
||||||
if in_game {
|
if in_game {
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
||||||
}
|
}
|
||||||
let sender = crate::handlers::RlnlSender::new(&conn.connection.sender);
|
let sender = crate::handlers::RlnlSender::new(&conn.connection.sender);
|
||||||
@@ -279,9 +314,9 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn broadcast<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
|
pub(super) async fn broadcast<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) {
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
if in_game {
|
if in_game {
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
||||||
}
|
}
|
||||||
let sender = conn.connection.rlnl();
|
let sender = conn.connection.rlnl();
|
||||||
@@ -295,9 +330,9 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn broadcast_dataless(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
|
pub(super) async fn broadcast_dataless(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) {
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
if in_game {
|
if in_game {
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
if !matches!(mode, ConnectionMode::InGame) { continue; }
|
||||||
}
|
}
|
||||||
let sender = conn.connection.rlnl();
|
let sender = conn.connection.rlnl();
|
||||||
@@ -328,7 +363,19 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
game_start != i64::MIN && game_end <= chrono::Utc::now().timestamp()
|
game_start != i64::MIN && game_end <= chrono::Utc::now().timestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub(super) async fn send_to_player<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, player_id: u8, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T) {
|
pub(super) fn players_info(&self) -> Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>> {
|
||||||
|
self.descriptors.values()
|
||||||
|
.map(|p| p.descriptor.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn real_player_count(&self) -> usize {
|
||||||
|
self.descriptors.values()
|
||||||
|
.filter(|p| p.descriptor.user_id.is_some())
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn send_to_player<T: byteserde::ser_heap::ByteSerializeHeap + ?Sized>(&self, player_id: u8, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T) {
|
||||||
if let Some(player) = self.users.read().await.get(&player_id) {
|
if let Some(player) = self.users.read().await.get(&player_id) {
|
||||||
crate::events::log_lnl_send_failure(player.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(player.connection.rlnl().send_data(
|
||||||
data,
|
data,
|
||||||
@@ -337,7 +384,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
&player.connection.connection,
|
&player.connection.connection,
|
||||||
).await);
|
).await);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
pub(super) fn spawn(self) -> tokio::sync::mpsc::Sender<super::GameMessage> {
|
pub(super) fn spawn(self) -> tokio::sync::mpsc::Sender<super::GameMessage> {
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(super::CHANNEL_BOUND);
|
let (tx, rx) = tokio::sync::mpsc::channel(super::CHANNEL_BOUND);
|
||||||
@@ -438,24 +485,22 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
inner: None,
|
inner: None,
|
||||||
})).unwrap_or_default();
|
})).unwrap_or_default();
|
||||||
} else {
|
} else {
|
||||||
let mut users = self.users.write().await;
|
|
||||||
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
//let id = users.len() as u8;
|
//let id = users.len() as u8;
|
||||||
let user_id = user.user_id();
|
let user_id = user.user_id();
|
||||||
let player_info = self.players_info.iter().find(|p| p.user_id == Some(user_id)).unwrap();
|
let player_info = self.descriptors.values().find(|p| p.descriptor.user_id == Some(user_id)).unwrap();
|
||||||
let id = player_info.player_id;
|
let id = player_info.descriptor.player_id;
|
||||||
|
let aliases = self.fakes_handler.get_client_ais().await.into_iter().find(|(key, _val)| *key == id).map(|(_key, val)| val).unwrap_or_default();
|
||||||
|
log::info!("AIs running on new player {}: {:?}", id, aliases);
|
||||||
let new_user = UserConnection {
|
let new_user = UserConnection {
|
||||||
user,
|
user,
|
||||||
connection: UserSender {
|
connection: UserSender {
|
||||||
connection,
|
connection,
|
||||||
sender,
|
sender,
|
||||||
},
|
},
|
||||||
state: std::sync::Arc::new(UserState::new()),
|
aliases,
|
||||||
machine: MachineState::new(),
|
|
||||||
descriptor: player_info.to_owned(),
|
|
||||||
counters: UserData::new(),
|
|
||||||
};
|
};
|
||||||
if self.custom_logic_handler.on_player_join(self, &new_user, &self.players_info).await {
|
if self.custom_logic_handler.on_player_join(self, &new_user, player_info).await {
|
||||||
//self.spawn_send_loading_events(&new_user, id, self.players_info.clone());
|
//self.spawn_send_loading_events(&new_user, id, self.players_info.clone());
|
||||||
crate::events::log_lnl_send_failure(new_user.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(new_user.connection.rlnl().send_data(
|
||||||
&rlnl::events::ingame::PlayerId { player: id },
|
&rlnl::events::ingame::PlayerId { player: id },
|
||||||
@@ -464,28 +509,55 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
&new_user.connection.connection
|
&new_user.connection.connection
|
||||||
).await);
|
).await);
|
||||||
log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid);
|
log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid);
|
||||||
self.user_id_map.write().await.insert(new_user.user.user_id(), id);
|
let new_user = std::sync::Arc::new(new_user);
|
||||||
users.insert(id, new_user);
|
let mut users = self.users.write().await;
|
||||||
|
users.insert(id, new_user.clone());
|
||||||
|
for fake_id in new_user.aliases.iter() {
|
||||||
|
if let Some(player_desc) = self.user_descriptor(*fake_id) {
|
||||||
|
if self.custom_logic_handler.on_player_join(self, &new_user, player_desc).await {
|
||||||
|
users.insert(*fake_id, new_user.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::warn!("Non-existent fake player id {} was encountered while connecting, ignoring", *fake_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response.send(None).unwrap_or_default();
|
response.send(None).unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_end_connection(&self, user_id: i32) -> bool {
|
async fn on_end_connection(&self, user_id: i32) -> bool {
|
||||||
if let Some(player_id) = self.user_key_by_user_id(user_id).await {
|
if let Some(player_id) = self.user_key_by_user_id(user_id) {
|
||||||
let conn_opt = self.users.write().await.remove(&player_id);
|
let conn_opt = self.users.write().await.remove(&player_id);
|
||||||
if let Some(conn) = conn_opt {
|
if let Some(conn) = conn_opt {
|
||||||
if self.custom_logic_handler.on_player_end(self, &conn).await {
|
let user_info = self.user_descriptor(player_id).unwrap();
|
||||||
conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
if self.custom_logic_handler.on_player_end(self, &conn, user_info).await {
|
||||||
if !self.is_complete.load(std::sync::atomic::Ordering::Relaxed) {
|
user_info.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
self.rebroadcast(
|
let mut disconnecting_players = Vec::with_capacity(conn.aliases.len() + 1);
|
||||||
user_id,
|
disconnecting_players.push(player_id);
|
||||||
rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected,
|
for fake_id in conn.aliases.iter() {
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
if let Some(user_desc) = self.user_descriptor(*fake_id) {
|
||||||
&rlnl::events::ingame::PlayerId { player: player_id },
|
user_desc.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
true,
|
let conn_opt = self.users.write().await.remove(&fake_id);
|
||||||
).await;
|
if let Some(conn) = conn_opt {
|
||||||
} else {
|
if self.custom_logic_handler.on_player_end(self, &conn, user_desc).await {
|
||||||
|
disconnecting_players.push(*fake_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let is_game_complete = self.is_complete.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
for disconnecter in disconnecting_players {
|
||||||
|
if !is_game_complete {
|
||||||
|
self.broadcast(
|
||||||
|
rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::PlayerId { player: disconnecter },
|
||||||
|
true,
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_game_complete {
|
||||||
// in every other case this packet would've already been sent
|
// in every other case this packet would've already been sent
|
||||||
// this makes the end-of-match "continue" button send you back to the main menu a bit sooner
|
// this makes the end-of-match "continue" button send you back to the main menu a bit sooner
|
||||||
// (otherwise it waits for the multiplayer server to disconnect via timeout)
|
// (otherwise it waits for the multiplayer server to disconnect via timeout)
|
||||||
@@ -495,8 +567,10 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
&conn.connection.connection,
|
&conn.connection.connection,
|
||||||
).await);
|
).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut has_active_connections = false;
|
let mut has_active_connections = false;
|
||||||
for user in self.users.read().await.values() {
|
for user in self.descriptors.values() {
|
||||||
|
if user.descriptor.user_id.is_none() { continue; } // skip non-players
|
||||||
let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
has_active_connections |= !matches!(mode, ConnectionMode::Disconnected);
|
has_active_connections |= !matches!(mode, ConnectionMode::Disconnected);
|
||||||
}
|
}
|
||||||
@@ -519,7 +593,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
|
|
||||||
async fn on_request_leave(&self, user_id: i32) {
|
async fn on_request_leave(&self, user_id: i32) {
|
||||||
log::info!("User {} wants to leave game {}", user_id, self.game_guid());
|
log::info!("User {} wants to leave game {}", user_id, self.game_guid());
|
||||||
if let Some(player_id) = self.user_key_by_user_id(user_id).await {
|
if let Some(player_id) = self.user_key_by_user_id(user_id) {
|
||||||
self.rebroadcast(
|
self.rebroadcast(
|
||||||
user_id,
|
user_id,
|
||||||
rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed,
|
rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed,
|
||||||
@@ -545,14 +619,15 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
user_name: rlnl::types::BinaryWriterString(user_name),
|
user_name: rlnl::types::BinaryWriterString(user_name),
|
||||||
progress,
|
progress,
|
||||||
};
|
};
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
|
let user_desc = self.user_descriptor(*player_id).unwrap();
|
||||||
if user_id == conn.user.user_id() {
|
if user_id == conn.user.user_id() {
|
||||||
let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100);
|
let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100);
|
||||||
log::info!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid());
|
log::info!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid());
|
||||||
conn.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed);
|
user_desc.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
match mode {
|
match mode {
|
||||||
ConnectionMode::Loading | ConnectionMode::Disconnected => {},
|
ConnectionMode::Loading | ConnectionMode::Disconnected => {},
|
||||||
ConnectionMode::WaitingForSync | ConnectionMode::Sync | ConnectionMode::WaitingToStart => {
|
ConnectionMode::WaitingForSync | ConnectionMode::Sync | ConnectionMode::WaitingToStart => {
|
||||||
@@ -572,40 +647,24 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
|
|
||||||
async fn on_request_loading_progress(&self, user_id: i32) {
|
async fn on_request_loading_progress(&self, user_id: i32) {
|
||||||
log::info!("Got request loading progress");
|
log::info!("Got request loading progress");
|
||||||
if let Some(user_key) = self.user_key_by_user_id(user_id).await {
|
if let Some(user_key) = self.user_key_by_user_id(user_id) {
|
||||||
if let Some(user_info) = self.users.read().await.get(&user_key) {
|
if let Some(user_info) = self.users.read().await.get(&user_key) {
|
||||||
self.spawn_send_loading_events(user_info, user_key, self.players_info.clone());
|
let mut client_ai_map = self.fakes_handler.get_client_ais().await;
|
||||||
|
self.spawn_send_loading_events(user_info, user_key, self.players_info(), client_ai_map.remove(&user_key).unwrap_or_default());
|
||||||
let sender = user_info.connection.rlnl();
|
let sender = user_info.connection.rlnl();
|
||||||
for conn in self.users.read().await.values() {
|
for user_desc in self.descriptors.values() {
|
||||||
if user_id == conn.user.user_id() { continue; }
|
if Some(user_id) == user_desc.descriptor.user_id { continue; }
|
||||||
/*crate::events::log_lnl_send_failure(sender.send_data(
|
|
||||||
&user_info.1,
|
|
||||||
rlnl::event_code::NetworkEvent::BroadcastLoadingProgress,
|
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
|
||||||
&user_info.0.connection,
|
|
||||||
).await);*/
|
|
||||||
let event = rlnl::events::loading::LoadingProgress {
|
let event = rlnl::events::loading::LoadingProgress {
|
||||||
user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()),
|
//user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()),
|
||||||
progress: (conn.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0,
|
user_name: rlnl::types::BinaryWriterString(user_desc.descriptor.public_id.clone()),
|
||||||
|
progress: (user_desc.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0,
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(sender.send_data(
|
crate::events::log_lnl_send_failure(sender.send_data(
|
||||||
&event,
|
&event,
|
||||||
rlnl::event_code::NetworkEvent::BroadcastLoadingProgress,
|
rlnl::event_code::NetworkEvent::BroadcastLoadingProgress,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&user_info.connection.connection,
|
&user_info.connection.connection,
|
||||||
).await)
|
).await);
|
||||||
}
|
|
||||||
for fake in self.fake_users.values() {
|
|
||||||
let event = rlnl::events::loading::LoadingProgress {
|
|
||||||
user_name: rlnl::types::BinaryWriterString(fake.descriptor.public_id.clone()),
|
|
||||||
progress: (fake.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0,
|
|
||||||
};
|
|
||||||
crate::events::log_lnl_send_failure(sender.send_data(
|
|
||||||
&event,
|
|
||||||
rlnl::event_code::NetworkEvent::BroadcastLoadingProgress,
|
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
|
||||||
&user_info.connection.connection,
|
|
||||||
).await)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!("Failed to find player {} in connected users for match {}", user_key, self.game_guid());
|
log::error!("Failed to find player {} in connected users for match {}", user_key, self.game_guid());
|
||||||
@@ -621,7 +680,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
category: oj_rc_core::data::weapon_list::ItemCategory,
|
category: oj_rc_core::data::weapon_list::ItemCategory,
|
||||||
size: oj_rc_core::data::cube_list::ItemTier
|
size: oj_rc_core::data::cube_list::ItemTier
|
||||||
) {
|
) {
|
||||||
if let Some(conn) = self.users.read().await.get(&machine_id) {
|
if let Some(conn) = self.user_descriptor(machine_id) {
|
||||||
let category_u32 = category as u32;
|
let category_u32 = category as u32;
|
||||||
let size_u32 = size as u32;
|
let size_u32 = size as u32;
|
||||||
conn.machine.selected_weapon.category.store(category_u32, std::sync::atomic::Ordering::Relaxed);
|
conn.machine.selected_weapon.category.store(category_u32, std::sync::atomic::Ordering::Relaxed);
|
||||||
@@ -644,39 +703,44 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
async fn on_request_loading_sync(&self, user_id: i32) {
|
async fn on_request_loading_sync(&self, user_id: i32) {
|
||||||
// wait for all users to be ready before transitioning to loading sync
|
// wait for all users to be ready before transitioning to loading sync
|
||||||
let mut ready_count = 0;
|
let mut ready_count = 0;
|
||||||
for user in self.users.read().await.values() {
|
for (player_id, user) in self.users.read().await.iter() {
|
||||||
|
let user_desc = self.user_descriptor(*player_id).unwrap();
|
||||||
if user.user.user_id() == user_id {
|
if user.user.user_id() == user_id {
|
||||||
if !matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) {
|
if !matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) {
|
||||||
log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id);
|
log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log::info!("User {} is awaiting sync", user_id);
|
log::info!("User {} is awaiting sync", user_id);
|
||||||
user.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
user_desc.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
ready_count += 1;
|
ready_count += 1;
|
||||||
} else if matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) {
|
} else if matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) {
|
||||||
ready_count += 1;
|
ready_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count();
|
let player_count = self.real_player_count();
|
||||||
if ready_count == player_count {
|
if ready_count == player_count {
|
||||||
log::info!("All players ({}) awaiting sync for game {}", player_count, self.game_guid());
|
log::info!("All players ({}) awaiting sync for game {}", player_count, self.game_guid());
|
||||||
for (user_key, conn) in self.users.read().await.iter() {
|
for (user_key, conn) in self.users.read().await.iter() {
|
||||||
let extra_packets = self.custom_logic_handler.extra_sync_events(self, conn).await;
|
let user_info = self.user_descriptor(*user_key).unwrap();
|
||||||
self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info.clone(), extra_packets, self.map_config.clone());
|
let extra_packets = self.custom_logic_handler.extra_sync_events(self, conn, user_info).await;
|
||||||
|
self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info(), extra_packets, self.map_config.clone());
|
||||||
|
let user_desc = self.user_descriptor(*user_key).unwrap();
|
||||||
|
user_desc.state.mode.store(ConnectionMode::Sync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_load_complete(&self, user_id: i32) {
|
async fn on_load_complete(&self, user_id: i32) {
|
||||||
if let Some(user_key) = self.user_key_by_user_id(user_id).await {
|
if let Some(user_key) = self.user_key_by_user_id(user_id) {
|
||||||
if let Some(conn) = self.users.read().await.get(&user_key) {
|
if let Some(conn) = self.users.read().await.get(&user_key) {
|
||||||
|
let user_desc = self.user_descriptor(user_key).unwrap();
|
||||||
log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key);
|
log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key);
|
||||||
conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed);
|
user_desc.state.progress.store(100, std::sync::atomic::Ordering::Relaxed);
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if !matches!(mode, ConnectionMode::Sync) {
|
if !matches!(mode, ConnectionMode::Sync) {
|
||||||
log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", conn.descriptor.player_id, mode);
|
log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", user_desc.descriptor.player_id, mode);
|
||||||
}
|
}
|
||||||
conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
user_desc.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
self.spawn_initial_ingame_events(conn, user_id);
|
self.spawn_initial_ingame_events(conn, user_id);
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid());
|
log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid());
|
||||||
@@ -688,13 +752,14 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
// wait for all users to be ready for starting game start countdown
|
// wait for all users to be ready for starting game start countdown
|
||||||
let mut all_users_loading_complete = true;
|
let mut all_users_loading_complete = true;
|
||||||
for conn in self.users.read().await.values() {
|
for player_info in self.descriptors.values() {
|
||||||
let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
if player_info.descriptor.user_id.is_none() { continue; } // skip non-user players
|
||||||
|
let mode = ConnectionMode::from_u8(player_info.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
all_users_loading_complete &= matches!(mode, ConnectionMode::WaitingToStart);
|
all_users_loading_complete &= matches!(mode, ConnectionMode::WaitingToStart);
|
||||||
}
|
}
|
||||||
// trigger game start
|
// trigger game start
|
||||||
if all_users_loading_complete {
|
if all_users_loading_complete {
|
||||||
let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count();
|
let player_count = self.real_player_count();
|
||||||
log::info!("All players ({}) are ready for game {}", player_count, self.game_guid());
|
log::info!("All players ({}) are ready for game {}", player_count, self.game_guid());
|
||||||
tokio::time::sleep(Self::END_OF_SYNC_DELAY).await;
|
tokio::time::sleep(Self::END_OF_SYNC_DELAY).await;
|
||||||
self.fakes_handler.on_ready(
|
self.fakes_handler.on_ready(
|
||||||
@@ -705,8 +770,9 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION;
|
let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION;
|
||||||
if self.custom_logic_handler.on_countdown_start(self, game_start).await {
|
if self.custom_logic_handler.on_countdown_start(self, game_start).await {
|
||||||
let mut senders = Vec::new();
|
let mut senders = Vec::new();
|
||||||
for conn in self.users.read().await.values() {
|
for (player_id, conn) in self.users.read().await.iter() {
|
||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
let user_desc = self.user_descriptor(*player_id).unwrap();
|
||||||
|
senders.push((conn.connection.clone(), user_desc.state.clone()));
|
||||||
}
|
}
|
||||||
self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||||
super::countdown::match_countdown(senders, game_start);
|
super::countdown::match_countdown(senders, game_start);
|
||||||
@@ -742,7 +808,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
log::info!("Player {} was destroyed by player {} (user {}) in game {}", remote_player, killer_player, user_id, self.game_guid());
|
log::info!("Player {} was destroyed by player {} (user {}) in game {}", remote_player, killer_player, user_id, self.game_guid());
|
||||||
if self.custom_logic_handler.on_vehicle_destroyed(self, killer_player, remote_player).await {
|
if self.custom_logic_handler.on_vehicle_destroyed(self, killer_player, remote_player).await {
|
||||||
// the kill tracking is initiated separately by the client with kill bonus event
|
// the kill tracking is initiated separately by the client with kill bonus event
|
||||||
if let Some(killed) = self.users.read().await.get(&remote_player) {
|
if let Some(killed) = self.user_descriptor(remote_player) {
|
||||||
killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None);
|
let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None);
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
@@ -756,7 +822,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn on_self_destruct(&self, user_id: i32, is_classic: bool) {
|
async fn on_self_destruct(&self, user_id: i32, is_classic: bool) {
|
||||||
if let Some(player_id) = self.user_key_by_user_id(user_id).await {
|
if let Some(player_id) = self.user_key_by_user_id(user_id) {
|
||||||
self.rebroadcast(
|
self.rebroadcast(
|
||||||
user_id,
|
user_id,
|
||||||
rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed,
|
rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed,
|
||||||
@@ -780,7 +846,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&conn.connection.connection
|
||||||
).await);
|
).await);
|
||||||
conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
let user_desc = self.user_descriptor(player_id).unwrap();
|
||||||
|
user_desc.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
conn.connection.connection.disconnect();
|
conn.connection.connection.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -789,7 +856,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn on_flipping_started(&self, user_id: i32) {
|
async fn on_flipping_started(&self, user_id: i32) {
|
||||||
if let Some(user_key) = self.user_key_by_user_id(user_id).await {
|
if let Some(user_key) = self.user_key_by_user_id(user_id) {
|
||||||
self.rebroadcast(
|
self.rebroadcast(
|
||||||
user_id,
|
user_id,
|
||||||
rlnl::event_code::NetworkEvent::AlignmentRectifierStarted,
|
rlnl::event_code::NetworkEvent::AlignmentRectifierStarted,
|
||||||
@@ -802,7 +869,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
|
|
||||||
async fn on_map_ping(&self, _user_id: i32, ping: rlnl::events::ingame::MapPing) {
|
async fn on_map_ping(&self, _user_id: i32, ping: rlnl::events::ingame::MapPing) {
|
||||||
for (id, conn) in self.users.read().await.iter() {
|
for (id, conn) in self.users.read().await.iter() {
|
||||||
if (*id as i32) != ping.sender && conn.descriptor.team == ping.team_id {
|
let user_desc = self.user_descriptor(*id).unwrap();
|
||||||
|
if (*id as i32) != ping.sender && user_desc.descriptor.team == ping.team_id {
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
&ping,
|
&ping,
|
||||||
rlnl::event_code::NetworkEvent::MapPingEvent,
|
rlnl::event_code::NetworkEvent::MapPingEvent,
|
||||||
@@ -820,7 +888,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
) {
|
) {
|
||||||
if self.custom_logic_handler.on_kill_bonus(self, shooter, shootee).await {
|
if self.custom_logic_handler.on_kill_bonus(self, shooter, shootee).await {
|
||||||
if let Some(to_reward) = self.users.read().await.get(&shooter) {
|
if let Some(to_reward) = self.users.read().await.get(&shooter) {
|
||||||
to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
let to_reward_desc = self.user_descriptor(shooter).unwrap();
|
||||||
|
to_reward_desc.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
||||||
&rlnl::events::ingame::Kill {
|
&rlnl::events::ingame::Kill {
|
||||||
killee_player_id: shootee,
|
killee_player_id: shootee,
|
||||||
@@ -830,7 +899,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&to_reward.connection.connection
|
&to_reward.connection.connection
|
||||||
).await);
|
).await);
|
||||||
let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None);
|
let data = to_reward_desc.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None);
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
@@ -849,7 +918,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
let lock = self.users.read().await;
|
let lock = self.users.read().await;
|
||||||
for shooter in shooters {
|
for shooter in shooters {
|
||||||
if let Some(to_reward) = lock.get(&shooter) {
|
if let Some(to_reward) = lock.get(&shooter) {
|
||||||
to_reward.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
let to_reward_desc = self.user_descriptor(shooter).unwrap();
|
||||||
|
to_reward_desc.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
||||||
&rlnl::events::ingame::Kill {
|
&rlnl::events::ingame::Kill {
|
||||||
killee_player_id: shootee,
|
killee_player_id: shootee,
|
||||||
@@ -859,7 +929,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&to_reward.connection.connection
|
&to_reward.connection.connection
|
||||||
).await);
|
).await);
|
||||||
let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None);
|
let data = to_reward_desc.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None);
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
@@ -874,12 +944,11 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
_user_id: i32,
|
_user_id: i32,
|
||||||
info: rlnl::events::ingame::DestroyedHealedCubesBonus
|
info: rlnl::events::ingame::DestroyedHealedCubesBonus
|
||||||
) {
|
) {
|
||||||
let lock = self.users.read().await;
|
|
||||||
for shooter in info.shooters {
|
for shooter in info.shooters {
|
||||||
if let Some(to_reward) = lock.get(&shooter.shooting_player_id) {
|
if let Some(to_reward) = self.user_descriptor(shooter.shooting_player_id) {
|
||||||
let mut total_cubes = 0;
|
let mut total_cubes = 0;
|
||||||
for target in shooter.shooter_targets {
|
for target in shooter.shooter_targets {
|
||||||
if let Some(to_punish) = lock.get(&target.target_player_id) {
|
if let Some(to_punish) = self.user_descriptor(target.target_player_id) {
|
||||||
let mut total_cubes_received = 0;
|
let mut total_cubes_received = 0;
|
||||||
for cubes in target.cube_amounts {
|
for cubes in target.cube_amounts {
|
||||||
// TODO use cube_id for something!?
|
// TODO use cube_id for something!?
|
||||||
@@ -905,12 +974,11 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
_user_id: i32,
|
_user_id: i32,
|
||||||
info: rlnl::events::ingame::DestroyedHealedCubesBonus,
|
info: rlnl::events::ingame::DestroyedHealedCubesBonus,
|
||||||
) {
|
) {
|
||||||
let lock = self.users.read().await;
|
|
||||||
for shooter in info.shooters {
|
for shooter in info.shooters {
|
||||||
if let Some(to_reward) = lock.get(&shooter.shooting_player_id) {
|
if let Some(to_reward) = self.user_descriptor(shooter.shooting_player_id) {
|
||||||
let mut total_cubes = 0;
|
let mut total_cubes = 0;
|
||||||
for target in shooter.shooter_targets {
|
for target in shooter.shooter_targets {
|
||||||
if let Some(to_punish) = lock.get(&target.target_player_id) {
|
if let Some(to_punish) = self.user_descriptor(target.target_player_id) {
|
||||||
let mut total_cubes_received = 0;
|
let mut total_cubes_received = 0;
|
||||||
for cubes in target.cube_amounts {
|
for cubes in target.cube_amounts {
|
||||||
// TODO use cube_id for something!?
|
// TODO use cube_id for something!?
|
||||||
@@ -984,10 +1052,10 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]);
|
let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]);
|
||||||
//log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4);
|
//log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4);
|
||||||
if self.custom_logic_handler.on_motion(self, &motion, (x4, y4, z4)).await {
|
if self.custom_logic_handler.on_motion(self, &motion, (x4, y4, z4)).await {
|
||||||
if let Some(conn) = self.users.read().await.get(&motion.player_id) {
|
if let Some(user_desc) = self.user_descriptor(motion.player_id) {
|
||||||
conn.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed);
|
user_desc.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed);
|
||||||
conn.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed);
|
user_desc.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed);
|
||||||
conn.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed);
|
user_desc.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed);
|
||||||
use byteserde::ser_heap::ByteSerializeHeap;
|
use byteserde::ser_heap::ByteSerializeHeap;
|
||||||
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
||||||
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
||||||
@@ -1011,19 +1079,19 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>) {
|
fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, client_ais: Vec<u8>) {
|
||||||
let connection = user.connection.clone();
|
let connection = user.connection.clone();
|
||||||
let user_id = user.user.user_id();
|
let user_id = user.user.user_id();
|
||||||
tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players));
|
tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players, client_ais));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_loading_events_wrapper(connection: UserSender, player_id: u8, user_id: i32, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>) {
|
async fn send_loading_events_wrapper(connection: UserSender, player_id: u8, user_id: i32, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, client_ais: Vec<u8>) {
|
||||||
if let Err(e) = Self::send_loading_events(&connection, player_id, players).await {
|
if let Err(e) = Self::send_loading_events(&connection, player_id, players, client_ais).await {
|
||||||
log::error!("Failed to send Loading events for user {} ({}): {}", user_id, player_id, e);
|
log::error!("Failed to send Loading events for user {} ({}): {}", user_id, player_id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_loading_events(user: &UserSender, _player_id: u8, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>) -> std::io::Result<()> {
|
async fn send_loading_events(user: &UserSender, _player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, client_ais: Vec<u8>) -> std::io::Result<()> {
|
||||||
//tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
//tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
let sender = user.rlnl();
|
let sender = user.rlnl();
|
||||||
/*sender.send_data(
|
/*sender.send_data(
|
||||||
@@ -1048,8 +1116,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
).await?;
|
).await?;
|
||||||
sender.send_data(
|
sender.send_data(
|
||||||
&rlnl::events::loading::PlayerIDs {
|
&rlnl::events::loading::PlayerIDs {
|
||||||
num_ids: 0,
|
num_ids: client_ais.len() as i32,
|
||||||
players: vec![],
|
players: client_ais.into_iter().map(|x| x as i32).collect(),
|
||||||
},
|
},
|
||||||
rlnl::event_code::NetworkEvent::HostAIs,
|
rlnl::event_code::NetworkEvent::HostAIs,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
@@ -1058,19 +1126,19 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_send_sync_events(&self, user: &UserConnection, user_id: i32, player_id: u8, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) {
|
fn spawn_send_sync_events(&self, user: &UserConnection, user_id: i32, player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) {
|
||||||
let connection = user.connection.clone();
|
let connection = user.connection.clone();
|
||||||
tokio::spawn(Self::send_sync_events_wrapper(connection, user_id, player_id, players, extra_packets, map));
|
tokio::spawn(Self::send_sync_events_wrapper(connection, user_id, player_id, players, extra_packets, map));
|
||||||
user.state.mode.store(ConnectionMode::Sync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
//user.state.mode.store(ConnectionMode::Sync.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_sync_events_wrapper(connection: UserSender, user_id: i32, player_id: u8, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) {
|
async fn send_sync_events_wrapper(connection: UserSender, user_id: i32, player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) {
|
||||||
if let Err(e) = Self::send_sync_events(connection, player_id, players, extra_packets, map).await {
|
if let Err(e) = Self::send_sync_events(connection, player_id, players, extra_packets, map).await {
|
||||||
log::error!("Failed to send Sync events for user {}: {}", user_id, e);
|
log::error!("Failed to send Sync events for user {}: {}", user_id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_sync_events(connection: UserSender, _player_id: u8, players: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) -> std::io::Result<()> {
|
async fn send_sync_events(connection: UserSender, _player_id: u8, players: Vec<std::sync::Arc<oj_rc_core::persist::user::PlayerDescriptor>>, extra_packets: Vec<super::RlnlPacket>, map: std::sync::Arc<oj_rc_core::persist::config::MapConfig>) -> std::io::Result<()> {
|
||||||
let num_players = players.len() as u8;
|
let num_players = players.len() as u8;
|
||||||
let sender = connection.rlnl();
|
let sender = connection.rlnl();
|
||||||
sender.send_empty(
|
sender.send_empty(
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ use crate::matches::{modes::trackers::SurrenderGameTracker, CustomGameLogic};
|
|||||||
|
|
||||||
struct PlayerTracker {
|
struct PlayerTracker {
|
||||||
connected: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
|
connected: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
|
||||||
in_point: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicU16>>, // player_id -> in point state (if val > u8::MAX then not in a point)
|
in_point: std::collections::HashMap<u8, std::sync::atomic::AtomicU16>, // player_id -> in point state (if val > u8::MAX then not in a point)
|
||||||
respawning: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicI64>>, // player_id -> time when they'll spawn (time since unix epoch)
|
respawning: std::collections::HashMap<u8, std::sync::atomic::AtomicI64>, // player_id -> time when they'll spawn (time since unix epoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerTracker {
|
impl PlayerTracker {
|
||||||
fn new() -> Self {
|
fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
connected: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
connected: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
in_point: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
in_point: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect(),
|
||||||
respawning: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
respawning: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN))).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ impl PlayerTracker {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn swap_is_in_point(&self, player_id: u8, point: Option<u8>) -> Option<u8> {
|
fn swap_is_in_point(&self, player_id: u8, point: Option<u8>) -> Option<u8> {
|
||||||
self.in_point.read().await.get(&player_id).and_then(|x| {
|
self.in_point.get(&player_id).and_then(|x| {
|
||||||
let old_point = x.swap(point.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
|
let old_point = x.swap(point.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
|
||||||
if old_point > u8::MAX as u16 {
|
if old_point > u8::MAX as u16 {
|
||||||
None
|
None
|
||||||
@@ -44,8 +44,6 @@ impl PlayerTracker {
|
|||||||
new_team.insert(player.player_id);
|
new_team.insert(player.player_id);
|
||||||
conn_lock.insert(player.team as u8, new_team);
|
conn_lock.insert(player.team as u8, new_team);
|
||||||
}
|
}
|
||||||
self.in_point.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
|
|
||||||
self.respawning.write().await.insert(player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disconnect_player(&self, player_id: u8) {
|
async fn disconnect_player(&self, player_id: u8) {
|
||||||
@@ -898,14 +896,14 @@ impl BattleArenaLogic {
|
|||||||
const CRYSTAL_ID: u32 = 3950293873;
|
const CRYSTAL_ID: u32 = 3950293873;
|
||||||
const CLASP_ID: u32 = 606866102;
|
const CLASP_ID: u32 = 606866102;
|
||||||
|
|
||||||
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
|
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, players: &[oj_rc_core::persist::user::PlayerDescriptor], ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
|
||||||
let cube_parser = parsers.locations_of();
|
let cube_parser = parsers.locations_of();
|
||||||
let crystals = cube_parser.locations_of_by_distance_to_first(&mut std::io::Cursor::new(&ba_config.base_machine_map), Self::CRYSTAL_ID, Self::CLASP_ID);
|
let crystals = cube_parser.locations_of_by_distance_to_first(&mut std::io::Cursor::new(&ba_config.base_machine_map), Self::CRYSTAL_ID, Self::CLASP_ID);
|
||||||
Self {
|
Self {
|
||||||
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
||||||
respawn_heal_duration: config.respawn_heal_duration,
|
respawn_heal_duration: config.respawn_heal_duration,
|
||||||
timer_task: tokio::sync::Mutex::new(None),
|
timer_task: tokio::sync::Mutex::new(None),
|
||||||
player_tracking: PlayerTracker::new(),
|
player_tracking: PlayerTracker::new(players),
|
||||||
capture_tracking: PointTracker::new(map.capture_points.iter().map(|(_, speed)| *speed)),
|
capture_tracking: PointTracker::new(map.capture_points.iter().map(|(_, speed)| *speed)),
|
||||||
surrender_tracking: super::trackers::SurrenderGameTracker::new(),
|
surrender_tracking: super::trackers::SurrenderGameTracker::new(),
|
||||||
base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config),
|
base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config),
|
||||||
@@ -989,7 +987,8 @@ impl BattleArenaLogic {
|
|||||||
winning_team,
|
winning_team,
|
||||||
end_reason,
|
end_reason,
|
||||||
};
|
};
|
||||||
for player in generic.users.read().await.values() {
|
for (player_id, player) in generic.user_descriptors().iter() {
|
||||||
|
if player.descriptor.user_id.is_none() { continue; } // skip non-user players
|
||||||
let is_winner = player.descriptor.team == winning_team as i32;
|
let is_winner = player.descriptor.team == winning_team as i32;
|
||||||
let net_event = match ty {
|
let net_event = match ty {
|
||||||
WinMode::BaseFull
|
WinMode::BaseFull
|
||||||
@@ -1001,21 +1000,18 @@ impl BattleArenaLogic {
|
|||||||
if is_winner { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost }
|
if is_winner { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(
|
generic.send_to_player(
|
||||||
player.connection.rlnl()
|
*player_id,
|
||||||
.send_data(
|
net_event,
|
||||||
&payload,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
net_event,
|
&payload,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
).await;
|
||||||
&player.connection.connection,
|
|
||||||
).await
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_destruct_tasks(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player_id: u8) {
|
async fn do_destruct_tasks(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player_id: u8) {
|
||||||
if let Some(player_team) = self.player_tracking.team(player_id).await {
|
if let Some(player_team) = self.player_tracking.team(player_id).await {
|
||||||
let was_in_point = self.player_tracking.swap_is_in_point(player_id, None).await;
|
let was_in_point = self.player_tracking.swap_is_in_point(player_id, None);
|
||||||
if let Some(was_in_point) = was_in_point {
|
if let Some(was_in_point) = was_in_point {
|
||||||
self.capture_tracking.on_exit(generic, was_in_point, player_id, player_team as i8, self.config.num_segments as f32).await;
|
self.capture_tracking.on_exit(generic, was_in_point, player_id, player_team as i8, self.config.num_segments as f32).await;
|
||||||
}
|
}
|
||||||
@@ -1027,7 +1023,7 @@ impl BattleArenaLogic {
|
|||||||
let respawn_time = std::time::Duration::from_secs(self.config.respawn_time_seconds as u64);
|
let respawn_time = std::time::Duration::from_secs(self.config.respawn_time_seconds as u64);
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let respawn_timestamp = now + respawn_time;
|
let respawn_timestamp = now + respawn_time;
|
||||||
if let Some(player_respawn) = self.player_tracking.respawning.read().await.get(&player_id) {
|
if let Some(player_respawn) = self.player_tracking.respawning.get(&player_id) {
|
||||||
player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
let respawn_payload = rlnl::events::ingame::RespawnTime {
|
let respawn_payload = rlnl::events::ingame::RespawnTime {
|
||||||
@@ -1142,13 +1138,13 @@ impl BattleArenaLogic {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CustomGameLogic for BattleArenaLogic {
|
impl CustomGameLogic for BattleArenaLogic {
|
||||||
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
|
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
log::info!("Player {} joined", player.descriptor.player_id);
|
log::info!("Player {} joined", player.descriptor.player_id);
|
||||||
self.player_tracking.track_player(&player.descriptor).await;
|
self.player_tracking.track_player(&player.descriptor).await;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool {
|
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
if generic.is_game_done() {
|
if generic.is_game_done() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1177,7 +1173,7 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
|
||||||
vec![
|
vec![
|
||||||
Some(crate::matches::RlnlPacket {
|
Some(crate::matches::RlnlPacket {
|
||||||
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
||||||
@@ -1336,8 +1332,9 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
||||||
let read_lock = generic.users.read().await;
|
let read_lock = generic.users.read().await;
|
||||||
let mut senders = Vec::with_capacity(read_lock.len());
|
let mut senders = Vec::with_capacity(read_lock.len());
|
||||||
for conn in read_lock.values() {
|
for (player_id, conn) in read_lock.iter() {
|
||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
|
||||||
|
senders.push((conn.connection.clone(), state));
|
||||||
}
|
}
|
||||||
drop(read_lock);
|
drop(read_lock);
|
||||||
let game_end = game_start + generic.game_duration;
|
let game_end = game_start + generic.game_duration;
|
||||||
@@ -1465,7 +1462,7 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point).await;
|
let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point);
|
||||||
if was_in_point != now_in_point {
|
if was_in_point != now_in_point {
|
||||||
//log::info!("Player {}'s occupied capture point changed from {:?} to {:?}", motion.player_id, was_in_point, now_in_point);
|
//log::info!("Player {}'s occupied capture point changed from {:?} to {:?}", motion.player_id, was_in_point, now_in_point);
|
||||||
if let Some(now_in_point) = now_in_point {
|
if let Some(now_in_point) = now_in_point {
|
||||||
@@ -1539,7 +1536,7 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
|
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
|
||||||
if let Some(player_id) = generic.user_key_by_user_id(user_id).await {
|
if let Some(player_id) = generic.user_key_by_user_id(user_id) {
|
||||||
if let Some(team) = self.player_tracking.team(player_id).await {
|
if let Some(team) = self.player_tracking.team(player_id).await {
|
||||||
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
|
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
|
||||||
if let Some(vote) = maybe_vote {
|
if let Some(vote) = maybe_vote {
|
||||||
@@ -1555,7 +1552,7 @@ impl CustomGameLogic for BattleArenaLogic {
|
|||||||
(rlnl::event_code::NetworkEvent::AwardTeamBaseProtoniumDestroyedRequest, literustlib::packet::Property::ReliableOrdered) => {
|
(rlnl::event_code::NetworkEvent::AwardTeamBaseProtoniumDestroyedRequest, literustlib::packet::Property::ReliableOrdered) => {
|
||||||
let maybe_crystal_bonus = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::AwardProtoniumDestroyedCubes>(data.as_ref());
|
let maybe_crystal_bonus = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::AwardProtoniumDestroyedCubes>(data.as_ref());
|
||||||
if let Some(crystal_destroyed) = maybe_crystal_bonus {
|
if let Some(crystal_destroyed) = maybe_crystal_bonus {
|
||||||
if let Some(generic_player) = generic.users.read().await.get(&crystal_destroyed.player_id) {
|
if let Some(generic_player) = generic.user_descriptor(crystal_destroyed.player_id) {
|
||||||
generic_player.counters.crystals.fetch_add(crystal_destroyed.destroyed_cubes as u32, std::sync::atomic::Ordering::Relaxed);
|
generic_player.counters.crystals.fetch_add(crystal_destroyed.destroyed_cubes as u32, std::sync::atomic::Ordering::Relaxed);
|
||||||
let data = generic_player.counters.get_generic_packet(crystal_destroyed.player_id, rlnl::types::IngameStatId::DestroyedProtoniumCubes, Some(crystal_destroyed.destroyed_cubes as _));
|
let data = generic_player.counters.get_generic_packet(crystal_destroyed.player_id, rlnl::types::IngameStatId::DestroyedProtoniumCubes, Some(crystal_destroyed.destroyed_cubes as _));
|
||||||
generic.broadcast(
|
generic.broadcast(
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ use crate::matches::CustomGameLogic;
|
|||||||
|
|
||||||
struct PlayerTracker {
|
struct PlayerTracker {
|
||||||
alive: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
|
alive: tokio::sync::Mutex<std::collections::HashMap<u8, std::collections::HashSet<u8>>>, // team -> set of player_id
|
||||||
in_base: tokio::sync::RwLock<std::collections::HashMap<u8, std::sync::atomic::AtomicU16>>, // player_id -> in base state (if base > u8::MAX then not in a base)
|
in_base: std::collections::HashMap<u8, std::sync::atomic::AtomicU16>, // player_id -> in base state (if base > u8::MAX then not in a base)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerTracker {
|
impl PlayerTracker {
|
||||||
|
fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
|
||||||
|
Self {
|
||||||
|
alive: tokio::sync::Mutex::new(std::collections::HashMap::with_capacity(2)),
|
||||||
|
in_base: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn track_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
async fn track_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
||||||
let mut alive_lock = self.alive.lock().await;
|
let mut alive_lock = self.alive.lock().await;
|
||||||
if let Some(team) = alive_lock.get_mut(&(player.team as u8)) {
|
if let Some(team) = alive_lock.get_mut(&(player.team as u8)) {
|
||||||
@@ -15,7 +22,7 @@ impl PlayerTracker {
|
|||||||
new_team.insert(player.player_id);
|
new_team.insert(player.player_id);
|
||||||
alive_lock.insert(player.team as u8, new_team);
|
alive_lock.insert(player.team as u8, new_team);
|
||||||
}
|
}
|
||||||
self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
|
//self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) {
|
||||||
@@ -67,8 +74,8 @@ impl PlayerTracker {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn swap_is_in_base(&self, player_id: u8, base: Option<u8>) -> Option<u8> {
|
fn swap_is_in_base(&self, player_id: u8, base: Option<u8>) -> Option<u8> {
|
||||||
self.in_base.read().await.get(&player_id).and_then(|x| {
|
self.in_base.get(&player_id).and_then(|x| {
|
||||||
let base = x.swap(base.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
|
let base = x.swap(base.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed);
|
||||||
if base > u8::MAX as u16 {
|
if base > u8::MAX as u16 {
|
||||||
None
|
None
|
||||||
@@ -288,18 +295,18 @@ impl BaseTracker {
|
|||||||
winning_team,
|
winning_team,
|
||||||
end_reason: rlnl::types::GameEndReason::BaseCaptured,
|
end_reason: rlnl::types::GameEndReason::BaseCaptured,
|
||||||
};
|
};
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, player_info) in generic.user_descriptors().iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
let event = if player_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWon
|
rlnl::event_code::NetworkEvent::GameWon
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLost
|
rlnl::event_code::NetworkEvent::GameLost
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
&win_data,
|
*player_id,
|
||||||
event,
|
event,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&win_data,
|
||||||
).await);
|
).await;
|
||||||
}
|
}
|
||||||
generic.game_done();
|
generic.game_done();
|
||||||
break;
|
break;
|
||||||
@@ -335,12 +342,9 @@ pub struct EliminationLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EliminationLogic {
|
impl EliminationLogic {
|
||||||
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig) -> Self {
|
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tracked: PlayerTracker {
|
tracked: PlayerTracker::new(players),
|
||||||
alive: tokio::sync::Mutex::new(std::collections::HashMap::new()),
|
|
||||||
in_base: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
|
||||||
},
|
|
||||||
bases: BaseTracker {
|
bases: BaseTracker {
|
||||||
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
|
bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(),
|
||||||
ticker: super::trackers::TickTracker::new(),
|
ticker: super::trackers::TickTracker::new(),
|
||||||
@@ -361,12 +365,12 @@ impl EliminationLogic {
|
|||||||
*lock = None;
|
*lock = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, conn: &crate::matches::generic::UserConnection) {
|
async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) {
|
||||||
log::debug!("Everyone is dead, so long and thanks for all the fish");
|
log::debug!("Everyone is dead, so long and thanks for all the fish");
|
||||||
generic.game_done();
|
generic.game_done();
|
||||||
self.abort_timer_sync().await;
|
self.abort_timer_sync().await;
|
||||||
let data = rlnl::events::ingame::GameLoseWin {
|
let data = rlnl::events::ingame::GameLoseWin {
|
||||||
winning_team: if conn.descriptor.team == 0 { 1 } else { 0 }, // always the other team
|
winning_team: if player.descriptor.team == 0 { 1 } else { 0 }, // always the other team
|
||||||
end_reason: rlnl::types::GameEndReason::NoPlayersRemaining,
|
end_reason: rlnl::types::GameEndReason::NoPlayersRemaining,
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
||||||
@@ -385,8 +389,10 @@ impl EliminationLogic {
|
|||||||
end_reason: rlnl::types::GameEndReason::OneTeamRemaining,
|
end_reason: rlnl::types::GameEndReason::OneTeamRemaining,
|
||||||
};
|
};
|
||||||
let winning_team_i32 = winning_team as i32;
|
let winning_team_i32 = winning_team as i32;
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, conn) in generic.users.read().await.iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
let user_info = generic.user_descriptor(*player_id).unwrap();
|
||||||
|
if user_info.descriptor.user_id.is_none() { continue; } // skip non-user players
|
||||||
|
let event = if user_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWon
|
rlnl::event_code::NetworkEvent::GameWon
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLost
|
rlnl::event_code::NetworkEvent::GameLost
|
||||||
@@ -403,12 +409,12 @@ impl EliminationLogic {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CustomGameLogic for EliminationLogic {
|
impl CustomGameLogic for EliminationLogic {
|
||||||
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
|
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
self.tracked.track_vehicle(&player.descriptor).await;
|
self.tracked.track_vehicle(&player.descriptor).await;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, player: &crate::matches::generic::UserConnection) -> bool {
|
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
if generic.is_game_done() {
|
if generic.is_game_done() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -422,14 +428,14 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id);
|
log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id);
|
||||||
self.send_win_info(generic, winning_team).await;
|
self.send_win_info(generic, winning_team).await;
|
||||||
} else if self.tracked.alive_count().await.is_empty() {
|
} else if self.tracked.alive_count().await.is_empty() {
|
||||||
self.on_last_player_gone(generic, player).await;
|
self.on_last_player_gone(generic, connection, player).await;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _killer: u8, victim: u8) -> bool {
|
async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _killer: u8, victim: u8) -> bool {
|
||||||
if let Some(conn) = generic.users.read().await.get(&victim) {
|
if let Some(victim_info) = generic.user_descriptor(victim) {
|
||||||
self.tracked.destroy_vehicle(&conn.descriptor).await;
|
self.tracked.destroy_vehicle(&victim_info.descriptor).await;
|
||||||
let final_score = rlnl::events::ingame::SetFinalGameScore {
|
let final_score = rlnl::events::ingame::SetFinalGameScore {
|
||||||
player_id: victim,
|
player_id: victim,
|
||||||
score: 42,
|
score: 42,
|
||||||
@@ -446,7 +452,10 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
} else {
|
} else {
|
||||||
log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid());
|
log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid());
|
||||||
if self.tracked.alive_count().await.is_empty() {
|
if self.tracked.alive_count().await.is_empty() {
|
||||||
self.on_last_player_gone(generic, conn).await;
|
if let Some(user_conn) = generic.users.read().await.get(&victim) {
|
||||||
|
self.on_last_player_gone(generic, user_conn, victim_info).await;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,7 +475,7 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
|
||||||
vec![
|
vec![
|
||||||
crate::matches::RlnlPacket {
|
crate::matches::RlnlPacket {
|
||||||
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
||||||
@@ -487,8 +496,9 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, game_start: chrono::DateTime<chrono::Utc>) -> bool {
|
||||||
let read_lock = generic.users.read().await;
|
let read_lock = generic.users.read().await;
|
||||||
let mut senders = Vec::with_capacity(read_lock.len());
|
let mut senders = Vec::with_capacity(read_lock.len());
|
||||||
for conn in read_lock.values() {
|
for (player_id, conn) in read_lock.iter() {
|
||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
|
||||||
|
senders.push((conn.connection.clone(), state));
|
||||||
}
|
}
|
||||||
drop(read_lock);
|
drop(read_lock);
|
||||||
let game_end = game_start + generic.game_duration;
|
let game_end = game_start + generic.game_duration;
|
||||||
@@ -552,7 +562,7 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base).await;
|
let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base);
|
||||||
if now_in_base != was_in_base {
|
if now_in_base != was_in_base {
|
||||||
if let Some(was_in_base) = was_in_base {
|
if let Some(was_in_base) = was_in_base {
|
||||||
self.bases.on_exit(generic, was_in_base, player_team == was_in_base, motion.player_id).await;
|
self.bases.on_exit(generic, was_in_base, player_team == was_in_base, motion.player_id).await;
|
||||||
@@ -572,7 +582,3 @@ impl CustomGameLogic for EliminationLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawn points (best guess)
|
|
||||||
// Mars 1: (16, 0, 19) and (355, 7, 372)
|
|
||||||
// Earth vanguard 2: (-248, 10, -251) and (267, 10, 258)
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ pub struct NoOpLogic;
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CustomGameLogic for NoOpLogic {
|
impl CustomGameLogic for NoOpLogic {
|
||||||
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
|
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
|
async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ impl CustomGameLogic for NoOpLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
|
||||||
Vec::default()
|
Vec::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,18 +23,18 @@ impl WinTracker {
|
|||||||
end_reason: rlnl::types::GameEndReason::PitMaxKillsAchieved,
|
end_reason: rlnl::types::GameEndReason::PitMaxKillsAchieved,
|
||||||
};
|
};
|
||||||
let winning_team_i32 = winning_team as i32;
|
let winning_team_i32 = winning_team as i32;
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, player_info) in generic.user_descriptors().iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
let event = if player_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWon
|
rlnl::event_code::NetworkEvent::GameWon
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLost
|
rlnl::event_code::NetworkEvent::GameLost
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
&data,
|
*player_id,
|
||||||
event,
|
event,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&data,
|
||||||
).await);
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ impl WinTracker {
|
|||||||
for (player_id, streak) in game.player_tracking.streaks.iter() {
|
for (player_id, streak) in game.player_tracking.streaks.iter() {
|
||||||
let player_streak = streak.load(std::sync::atomic::Ordering::Relaxed);
|
let player_streak = streak.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
if player_streak >= *streak_threshold {
|
if player_streak >= *streak_threshold {
|
||||||
if let Some(conn) = generic.users.read().await.get(player_id) {
|
if let Some(conn) = generic.user_descriptor(*player_id) {
|
||||||
log::info!("Player {} has reached the streak win condition in game {}", player_id, generic.game_guid());
|
log::info!("Player {} has reached the streak win condition in game {}", player_id, generic.game_guid());
|
||||||
Self::do_win(generic, game, conn.descriptor.team as u8).await;
|
Self::do_win(generic, game, conn.descriptor.team as u8).await;
|
||||||
break;
|
break;
|
||||||
@@ -56,28 +56,28 @@ impl WinTracker {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
oj_rc_core::persist::config::PitWinCondition::TotalKills(kills_threshold) => {
|
oj_rc_core::persist::config::PitWinCondition::TotalKills(kills_threshold) => {
|
||||||
for (player_id, conn) in generic.users.read().await.iter() {
|
for (player_id, player_info) in generic.user_descriptors() {
|
||||||
if conn.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold {
|
if player_info.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold {
|
||||||
log::info!("Player {} has reached the total kills win condition in game {}", player_id, generic.game_guid());
|
log::info!("Player {} has reached the total kills win condition in game {}", player_id, generic.game_guid());
|
||||||
Self::do_win(generic, game, conn.descriptor.team as u8).await;
|
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oj_rc_core::persist::config::PitWinCondition::Score(score_threshold) => {
|
oj_rc_core::persist::config::PitWinCondition::Score(score_threshold) => {
|
||||||
for (player_id, conn) in generic.users.read().await.iter() {
|
for (player_id, player_info) in generic.user_descriptors() {
|
||||||
if conn.counters.generic_score() >= *score_threshold {
|
if player_info.counters.generic_score() >= *score_threshold {
|
||||||
log::info!("Player {} has reached the total score win condition in game {}", player_id, generic.game_guid());
|
log::info!("Player {} has reached the total score win condition in game {}", player_id, generic.game_guid());
|
||||||
Self::do_win(generic, game, conn.descriptor.team as u8).await;
|
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oj_rc_core::persist::config::PitWinCondition::Damage(dmg_threshold) => {
|
oj_rc_core::persist::config::PitWinCondition::Damage(dmg_threshold) => {
|
||||||
for (player_id, conn) in generic.users.read().await.iter() {
|
for (player_id, player_info) in generic.user_descriptors() {
|
||||||
if conn.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold {
|
if player_info.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold {
|
||||||
log::info!("Player {} has reached the total damage win condition in game {}", player_id, generic.game_guid());
|
log::info!("Player {} has reached the total damage win condition in game {}", player_id, generic.game_guid());
|
||||||
Self::do_win(generic, game, conn.descriptor.team as u8).await;
|
Self::do_win(generic, game, player_info.descriptor.team as u8).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ impl PlayerTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pit_stats(&self, users: &std::collections::HashMap<u8, crate::matches::generic::UserConnection>) -> rlnl::events::ingame::PitModeState {
|
fn pit_stats(&self, users: &std::collections::HashMap<u8, crate::matches::generic::UserDescriptor>) -> rlnl::events::ingame::PitModeState {
|
||||||
let mut player_stats = Vec::with_capacity(self.streaks.len());
|
let mut player_stats = Vec::with_capacity(self.streaks.len());
|
||||||
for (player_id, streak) in self.streaks.iter() {
|
for (player_id, streak) in self.streaks.iter() {
|
||||||
if let Some(user) = users.get(player_id) {
|
if let Some(user) = users.get(player_id) {
|
||||||
@@ -188,7 +188,7 @@ impl PitLogic {
|
|||||||
log::warn!("Pit game {} has no leader", generic.game_guid());
|
log::warn!("Pit game {} has no leader", generic.game_guid());
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let killer_score = if let Some(user) = generic.users.read().await.get(&killer) {
|
let killer_score = if let Some(user) = generic.user_descriptor(killer) {
|
||||||
user.counters.generic_score()
|
user.counters.generic_score()
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Player {} score not found", killer);
|
log::warn!("Player {} score not found", killer);
|
||||||
@@ -216,7 +216,7 @@ impl PitLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn do_leaderboard_update(&self, generic: &crate::matches::GenericGamemodeEngine<Self>) {
|
async fn do_leaderboard_update(&self, generic: &crate::matches::GenericGamemodeEngine<Self>) {
|
||||||
let latest_stats = self.player_tracking.pit_stats(&*generic.users.read().await);
|
let latest_stats = self.player_tracking.pit_stats(generic.user_descriptors());
|
||||||
generic.broadcast(
|
generic.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::PitModeState,
|
rlnl::event_code::NetworkEvent::PitModeState,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
@@ -331,19 +331,20 @@ impl PitLogic {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CustomGameLogic for PitLogic {
|
impl CustomGameLogic for PitLogic {
|
||||||
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
|
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
|
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
if generic.is_game_done() {
|
if generic.is_game_done() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let read_lock = generic.users.read().await;
|
let read_lock = generic.users.read().await;
|
||||||
if read_lock.len() == 1 {
|
if read_lock.len() == 1 {
|
||||||
// nobody to play against, automatically end the game
|
// nobody to play against, automatically end the game
|
||||||
let last_player = &read_lock[&0];
|
let player_id = read_lock.keys().next().unwrap();
|
||||||
WinTracker::do_win(generic, self, last_player.descriptor.team as u8).await;
|
let user_info = generic.user_descriptor(*player_id).unwrap();
|
||||||
|
WinTracker::do_win(generic, self, user_info.descriptor.team as u8).await;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -361,9 +362,18 @@ impl CustomGameLogic for PitLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn on_kill_bonus(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool {
|
async fn on_kill_bonus(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, killer: u8, victim: u8) -> bool {
|
||||||
if let Some(to_reward) = generic.users.read().await.get(&killer) {
|
if let Some(to_reward) = generic.user_descriptor(killer) {
|
||||||
to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
|
killer,
|
||||||
|
rlnl::event_code::NetworkEvent::ConfirmedKill,
|
||||||
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
|
&rlnl::events::ingame::Kill {
|
||||||
|
killee_player_id: victim,
|
||||||
|
killer_player_id: killer,
|
||||||
|
},
|
||||||
|
).await;
|
||||||
|
/*crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data(
|
||||||
&rlnl::events::ingame::Kill {
|
&rlnl::events::ingame::Kill {
|
||||||
killee_player_id: victim,
|
killee_player_id: victim,
|
||||||
killer_player_id: killer,
|
killer_player_id: killer,
|
||||||
@@ -371,7 +381,7 @@ impl CustomGameLogic for PitLogic {
|
|||||||
rlnl::event_code::NetworkEvent::ConfirmedKill,
|
rlnl::event_code::NetworkEvent::ConfirmedKill,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&to_reward.connection.connection
|
&to_reward.connection.connection
|
||||||
).await);
|
).await);*/
|
||||||
let data = to_reward.counters.get_generic_packet(killer, rlnl::types::IngameStatId::Kill, None);
|
let data = to_reward.counters.get_generic_packet(killer, rlnl::types::IngameStatId::Kill, None);
|
||||||
generic.broadcast(
|
generic.broadcast(
|
||||||
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
rlnl::event_code::NetworkEvent::UpdateGameStats,
|
||||||
@@ -384,7 +394,7 @@ impl CustomGameLogic for PitLogic {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
|
||||||
let mut initial_spawn_packets = self.initial_spawns_to_packets();
|
let mut initial_spawn_packets = self.initial_spawns_to_packets();
|
||||||
initial_spawn_packets.push(
|
initial_spawn_packets.push(
|
||||||
crate::matches::RlnlPacket {
|
crate::matches::RlnlPacket {
|
||||||
@@ -403,8 +413,9 @@ impl CustomGameLogic for PitLogic {
|
|||||||
let read_lock = generic.users.read().await;
|
let read_lock = generic.users.read().await;
|
||||||
let game_end = game_start + generic.game_duration;
|
let game_end = game_start + generic.game_duration;
|
||||||
let mut senders = Vec::with_capacity(read_lock.len());
|
let mut senders = Vec::with_capacity(read_lock.len());
|
||||||
for conn in read_lock.values() {
|
for (player_id, conn) in read_lock.iter() {
|
||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
|
||||||
|
senders.push((conn.connection.clone(), state));
|
||||||
}
|
}
|
||||||
drop(read_lock);
|
drop(read_lock);
|
||||||
let end_packets = if self.settings.wins.iter().any(|cond| matches!(cond, oj_rc_core::persist::config::PitWinCondition::Time)) {
|
let end_packets = if self.settings.wins.iter().any(|cond| matches!(cond, oj_rc_core::persist::config::PitWinCondition::Time)) {
|
||||||
|
|||||||
@@ -86,18 +86,18 @@ impl ScoreTracker {
|
|||||||
end_reason: if was_sudden_death { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredSuddenDeath } else { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredMostKills },
|
end_reason: if was_sudden_death { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredSuddenDeath } else { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredMostKills },
|
||||||
};
|
};
|
||||||
let winning_team_i32 = winning_team as i32;
|
let winning_team_i32 = winning_team as i32;
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, player_info) in generic.user_descriptors().iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
let event = if player_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
&data,
|
*player_id,
|
||||||
event,
|
event,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&data,
|
||||||
).await);
|
).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.is_sudden_death.store(true, std::sync::atomic::Ordering::Relaxed);
|
self.is_sudden_death.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
@@ -111,18 +111,18 @@ impl ScoreTracker {
|
|||||||
end_reason: rlnl::types::GameEndReason::TeamDeathMatchMaxKillsAchieved,
|
end_reason: rlnl::types::GameEndReason::TeamDeathMatchMaxKillsAchieved,
|
||||||
};
|
};
|
||||||
let winning_team_i32 = winning_team as i32;
|
let winning_team_i32 = winning_team as i32;
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, player_info) in generic.user_descriptors().iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
let event = if player_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
&data,
|
*player_id,
|
||||||
event,
|
event,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&data,
|
||||||
).await);
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ impl PlayerTracker {
|
|||||||
|
|
||||||
async fn single_remaining_team(generic: &crate::matches::GenericGamemodeEngine<TeamDeathMatchLogic>) -> Option<u8> {
|
async fn single_remaining_team(generic: &crate::matches::GenericGamemodeEngine<TeamDeathMatchLogic>) -> Option<u8> {
|
||||||
let mut first_remaining_team = None;
|
let mut first_remaining_team = None;
|
||||||
for conn in generic.users.read().await.values() {
|
for conn in generic.user_descriptors().values() {
|
||||||
let mode = crate::matches::generic::ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
let mode = crate::matches::generic::ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
if matches!(mode, crate::matches::generic::ConnectionMode::InGame) {
|
if matches!(mode, crate::matches::generic::ConnectionMode::InGame) {
|
||||||
if let Some(first_remaining_team) = first_remaining_team {
|
if let Some(first_remaining_team) = first_remaining_team {
|
||||||
@@ -241,18 +241,19 @@ impl TeamDeathMatchLogic {
|
|||||||
end_reason,
|
end_reason,
|
||||||
};
|
};
|
||||||
let winning_team_i32 = winning_team as i32;
|
let winning_team_i32 = winning_team as i32;
|
||||||
for conn in generic.users.read().await.values() {
|
for (player_id, player_info) in generic.user_descriptors().iter() {
|
||||||
let event = if conn.descriptor.team == winning_team_i32 {
|
if player_info.descriptor.user_id.is_none() { continue; } // skip non-user players
|
||||||
|
let event = if player_info.descriptor.team == winning_team_i32 {
|
||||||
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameWonBaseDestroyed
|
||||||
} else {
|
} else {
|
||||||
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
rlnl::event_code::NetworkEvent::GameLostBaseDestroyed
|
||||||
};
|
};
|
||||||
crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data(
|
generic.send_to_player(
|
||||||
&data,
|
*player_id,
|
||||||
event,
|
event,
|
||||||
literustlib::packet::Property::ReliableOrdered,
|
literustlib::packet::Property::ReliableOrdered,
|
||||||
&conn.connection.connection
|
&data
|
||||||
).await);
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +275,7 @@ impl TeamDeathMatchLogic {
|
|||||||
&respawn_payload,
|
&respawn_payload,
|
||||||
true
|
true
|
||||||
).await;
|
).await;
|
||||||
if let Some(user) = generic.users.read().await.get(&player_id) {
|
if let Some(user) = generic.user_descriptor(player_id) {
|
||||||
let player_team = user.descriptor.team as u8;
|
let player_team = user.descriptor.team as u8;
|
||||||
let spawn_point = if let Some(team_spawns) = generic.map_config.spawns.get(&player_team) {
|
let spawn_point = if let Some(team_spawns) = generic.map_config.spawns.get(&player_team) {
|
||||||
if team_spawns.is_empty() {
|
if team_spawns.is_empty() {
|
||||||
@@ -304,15 +305,16 @@ impl TeamDeathMatchLogic {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CustomGameLogic for TeamDeathMatchLogic {
|
impl CustomGameLogic for TeamDeathMatchLogic {
|
||||||
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool {
|
async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine<Self>, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> bool {
|
async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool {
|
||||||
if generic.is_game_done() {
|
if generic.is_game_done() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if let Some(winning_team) = PlayerTracker::single_remaining_team(generic).await {
|
if let Some(winning_team) = PlayerTracker::single_remaining_team(generic).await {
|
||||||
|
log::info!("All players except those on team {} have disconnected, ending game {} early", winning_team, generic.game_guid());
|
||||||
self.do_win(WinReason::OutOfPlayers, generic, winning_team).await;
|
self.do_win(WinReason::OutOfPlayers, generic, winning_team).await;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -334,7 +336,7 @@ impl CustomGameLogic for TeamDeathMatchLogic {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _player: &crate::matches::generic::UserConnection) -> Vec<crate::matches::RlnlPacket> {
|
async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine<Self>, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec<crate::matches::RlnlPacket> {
|
||||||
vec![
|
vec![
|
||||||
crate::matches::RlnlPacket {
|
crate::matches::RlnlPacket {
|
||||||
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
event: rlnl::event_code::NetworkEvent::GameModeSettings,
|
||||||
@@ -355,8 +357,9 @@ impl CustomGameLogic for TeamDeathMatchLogic {
|
|||||||
let read_lock = generic.users.read().await;
|
let read_lock = generic.users.read().await;
|
||||||
let game_end = game_start + generic.game_duration;
|
let game_end = game_start + generic.game_duration;
|
||||||
let mut senders = Vec::with_capacity(read_lock.len());
|
let mut senders = Vec::with_capacity(read_lock.len());
|
||||||
for conn in read_lock.values() {
|
for (player_id, conn) in read_lock.iter() {
|
||||||
senders.push((conn.connection.clone(), conn.state.clone()));
|
let state = generic.user_descriptor(*player_id).unwrap().state.clone();
|
||||||
|
senders.push((conn.connection.clone(), state));
|
||||||
}
|
}
|
||||||
drop(read_lock);
|
drop(read_lock);
|
||||||
/*let end_packets = vec![
|
/*let end_packets = vec![
|
||||||
@@ -427,7 +430,7 @@ impl CustomGameLogic for TeamDeathMatchLogic {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
|
(rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => {
|
||||||
if let Some(player_id) = generic.user_key_by_user_id(user_id).await {
|
if let Some(player_id) = generic.user_key_by_user_id(user_id) {
|
||||||
if let Some(&team) = self.player_tracking.teams.get(&player_id) {
|
if let Some(&team) = self.player_tracking.teams.get(&player_id) {
|
||||||
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
|
let maybe_vote = <dyn core::any::Any>::downcast_ref::<rlnl::events::ingame::SurrenderVoteCast>(data.as_ref());
|
||||||
if let Some(vote) = maybe_vote {
|
if let Some(vote) = maybe_vote {
|
||||||
|
|||||||
Reference in New Issue
Block a user