1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Fix dominating message, fix fake player loading, mess around with reactor crystals

This commit is contained in:
NG (Graham)
2026-01-15 18:49:29 -05:00
parent 16f0761380
commit a48f9dcfe9
13 changed files with 222 additions and 52 deletions

16
Cargo.lock generated
View File

@@ -2424,6 +2424,22 @@ dependencies = [
"zip",
]
[[package]]
name = "oj_factory_api"
version = "1.1.0"
dependencies = [
"actix-web",
"base64",
"clap",
"env_logger",
"git-version",
"libfj",
"log",
"oj_rc_core",
"oj_rc_factory",
"serde_json",
]
[[package]]
name = "oj_polariton_auth"
version = "1.1.0"

View File

@@ -12264,7 +12264,8 @@
"active": true,
"protonium": true,
"visibility": "None",
"ignore_in_weapon_list": true
"ignore_in_weapon_list": true,
"health": 10000
},
"spriteName": "CubeThumb_LightChassis_Cube",
"nameStrKey": "strProtoniumCrystalName",

View File

@@ -42,33 +42,41 @@ impl CubeLocationsParser {
}
}
fn locations_sorted_by_distance_from_point(cubes: &[super::parser::Cube], point: (u8, u8, u8), locations_of_id: u32) -> Vec<CubeLocationInfo> {
let target_x = point.0 as f32;
let target_y = point.1 as f32;
let target_z = point.2 as f32;
let mut relevant_cubes: Vec<(f32, CubeLocationInfo)> = cubes.into_iter()
.filter(|x| x.id == locations_of_id)
.map(|cube| {
let distance = (
(cube.x as f32 - target_x).powi(2)
+ (cube.y as f32 - target_y).powi(2)
+ (cube.z as f32 - target_z).powi(2)
).sqrt();
(distance, CubeLocationInfo {
x: cube.x,
y: cube.y,
z: cube.z,
extras: cube.orientation,
})
})
.collect();
relevant_cubes.sort_by_key(|(distance, _)| (distance * 1_000_000.0) as i64);
relevant_cubes.into_iter()
.map(|(_, cube)| cube)
.collect()
}
pub fn locations_of_by_distance_to_first(&self, r: &mut dyn std::io::Read, locations_of_id: u32, distance_to_id: u32) -> Vec<CubeLocationInfo> {
match super::parser::Cube::parse_list(r) {
Ok(cubes) => {
if let Some(target) = cubes.iter().find(|x| x.id == distance_to_id) {
let target_x = target.x as f32;
let target_y = target.y as f32;
let target_z = target.z as f32;
let mut relevant_cubes: Vec<(f32, CubeLocationInfo)> = cubes.into_iter()
.filter(|x| x.id == locations_of_id)
.map(|cube| {
let distance = (
(cube.x as f32 - target_x).powi(2)
+ (cube.y as f32 - target_y).powi(2)
+ (cube.z as f32 - target_z).powi(2)
).sqrt();
(distance, CubeLocationInfo {
x: cube.x,
y: cube.y,
z: cube.z,
extras: cube.orientation,
})
})
.collect();
relevant_cubes.sort_by_key(|(distance, _)| (distance * 1_000_000.0) as i64);
relevant_cubes.into_iter()
.map(|(_, cube)| cube)
.collect()
Self::locations_sorted_by_distance_from_point(
&cubes,
(target.x, target.y, target.z),
locations_of_id,
)
} else {
log::warn!("No cube with id {} to calculate distance", distance_to_id);
cubes.into_iter()
@@ -89,4 +97,20 @@ impl CubeLocationsParser {
}
}
}
pub fn locations_of_by_distance_from(&self, r: &mut dyn std::io::Read, locations_of_id: u32, from: (u8, u8, u8)) -> Vec<CubeLocationInfo> {
match super::parser::Cube::parse_list(r) {
Ok(cubes) => {
Self::locations_sorted_by_distance_from_point(
&cubes,
from,
locations_of_id,
)
}
Err(e) => {
log::error!("Failed to parse cube data to find cube locations: {}", e);
Vec::default()
}
}
}
}

View File

@@ -336,7 +336,7 @@ fn default_game_modes() -> GameModes {
respawn_heal_duration: 10.0,
respawn_full_heal_duration: 0.5,
kill_limit: 0,
game_time_m: 2,
game_time_m: 20,
},
elimination: GameMode {
respawn_heal_duration: 10.0,
@@ -622,6 +622,7 @@ fn default_multiplayer() -> super::MultiplayerConfig {
autostart_after_s: 180,
network: super::multiplayer::default_net_conf(),
fakes: super::multiplayer::default_fake_users(),
filler: super::multiplayer::default_filler_users(),
battle_arena: super::multiplayer::default_ba_conf(),
pit_config: super::multiplayer::default_pit_conf(),
team_death_match: super::multiplayer::default_tdm_conf(),

View File

@@ -488,6 +488,14 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
}).collect()
}
fn filler_players(&self) -> Vec<super::FakePlayer> {
self.battle.multiplayer.filler.iter().map(|player| super::FakePlayer {
team: player.team,
vehicle: player.vehicle.into_conf(),
implementation: player.implementation.clone().to_config(),
}).collect()
}
fn energy(&self) -> super::EnergyConfig {
super::EnergyConfig {
refill_rate: self.battle.energy.refill_rate_per_s,

View File

@@ -34,6 +34,7 @@ pub trait ConfigProvider<C: Clone> {
fn maps(&self) -> std::collections::HashMap<GameMap, MapConfig>;
fn url_links(&self) -> LinksConfig;
fn fake_players(&self) -> Vec<FakePlayer>;
fn filler_players(&self) -> Vec<FakePlayer>;
fn energy(&self) -> EnergyConfig;
fn ba_settings(&self) -> BattleArenaResolver;
fn pit_settings(&self) -> PitSettings;

View File

@@ -9,6 +9,8 @@ pub struct MultiplayerConfig {
pub network: NetworkConf,
#[serde(default = "default_fake_users")]
pub fakes: Vec<FakePlayerConf>,
#[serde(default = "default_filler_users")]
pub filler: Vec<FakePlayerConf>,
#[serde(default = "default_ba_conf")]
pub battle_arena: BattleArenaConfig,
#[serde(default = "default_pit_conf")]
@@ -166,6 +168,10 @@ pub(super) fn default_fake_users() -> Vec<FakePlayerConf> {
]
}
pub(super) fn default_filler_users() -> Vec<FakePlayerConf> {
default_fake_users()
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "impl")]
pub enum ClientEmulation {
@@ -242,7 +248,7 @@ fn default_ba_base() -> super::garage::PrefabVehicle {
}
fn default_crystal_health() -> u32 {
1_000
10_000
}
fn default_respawn_time() -> u64 {

View File

@@ -8,6 +8,7 @@ pub struct AccountProvider {
cubes: std::sync::Arc<Vec<u32>>,
garage_upgrades: std::sync::Arc<crate::persist::config::GarageUpgrades>,
fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
filler_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
auto_signups: bool,
cdn: std::sync::Arc<String>,
auth: std::sync::Arc<String>,
@@ -30,6 +31,7 @@ impl AccountProvider {
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(conf)),
garage_upgrades: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::garage_upgrades(conf)),
fake_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::fake_players(conf)),
filler_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::filler_players(conf)),
auto_signups: server_settings.auto_signup,
cdn: std::sync::Arc::new(server_settings.cdn_url),
auth: std::sync::Arc::new(server_settings.auth_url),
@@ -113,6 +115,7 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
cubes: self.cubes.clone(),
garage_upgrades: self.garage_upgrades.clone(),
fake_players: self.fake_players.clone(),
filler_players: self.filler_players.clone(),
cdn: self.cdn.clone(),
auth: self.auth.clone(),
intercom: self.intercom.clone(),
@@ -151,6 +154,7 @@ impl <C: Clone + Send> super::UserProvider<C> for AccountProvider {
cubes: self.cubes.clone(),
garage_upgrades: self.garage_upgrades.clone(),
fake_players: self.fake_players.clone(),
filler_players: self.filler_players.clone(),
cdn: self.cdn.clone(),
auth: self.auth.clone(),
intercom: self.intercom.clone(),
@@ -316,6 +320,7 @@ pub(super) struct UserData {
pub(super) cubes: std::sync::Arc<Vec<u32>>,
pub(super) garage_upgrades: std::sync::Arc<crate::persist::config::GarageUpgrades>,
pub(super) fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
pub(super) filler_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
pub(super) cdn: std::sync::Arc<String>,
pub(super) auth: std::sync::Arc<String>,
pub(super) intercom: std::sync::Arc<String>,

View File

@@ -58,6 +58,7 @@ impl super::LobbyUser for UserData {
cpu_counter: &crate::cubes::CpuListParser,
weapon_lister: &crate::cubes::WeaponListParser,
chooser: &TeamChooser,
missing_players: usize,
) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
let now = chrono::Utc::now().timestamp();
let guid = crate::persist::user::str_to_i64(&game.guid)
@@ -73,7 +74,8 @@ impl super::LobbyUser for UserData {
oj_rc_database::schema::multiplayer_game::GameType::Standard
};
let fake_players = self.generate_fake_players_data(guid, &players, factory, cpu_counter, weapon_lister, chooser).await?;
let forced_fake_players = self.generate_forced_fake_players_data(guid, &players, factory, cpu_counter, weapon_lister, chooser).await?;
let filler_players = self.generate_filler_players_data(guid, &players, factory, cpu_counter, weapon_lister, chooser, missing_players).await?;
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
@@ -95,6 +97,7 @@ impl super::LobbyUser for UserData {
})?;
let players_len = players.len();
let forced_fake_players_len = forced_fake_players.len();
let players: Vec<oj_rc_database::schema::multiplayer_game_player::ActiveModel> = players.into_iter()
.enumerate()
@@ -113,7 +116,7 @@ impl super::LobbyUser for UserData {
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game_player::ClientType::Client),
}
})
.chain(fake_players.iter()
.chain(forced_fake_players.iter()
.enumerate()
.map(|(i, (fake, variant))| {
oj_rc_database::schema::multiplayer_game_player::ActiveModel {
@@ -131,6 +134,24 @@ impl super::LobbyUser for UserData {
}
})
)
.chain(filler_players.iter()
.enumerate()
.map(|(i, (fake, variant))| {
oj_rc_database::schema::multiplayer_game_player::ActiveModel {
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
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),
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
player_id: oj_rc_database::sea_orm::ActiveValue::Set(((i + players_len + forced_fake_players_len) as u8) as _),
team: oj_rc_database::sea_orm::ActiveValue::Set(fake.team),
group: oj_rc_database::sea_orm::ActiveValue::Set(None),
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(true),
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()),
variant: oj_rc_database::sea_orm::ActiveValue::Set(fake_impl_to_db(variant)),
}
})
)
.collect();
self.db.insert_players(players).await.map_err(|e| {
log::error!("Failed to create game players for {} through user_id {}: {}", game.guid, self.account.id, e);
@@ -140,6 +161,8 @@ impl super::LobbyUser for UserData {
)
})?;
Ok(super::FakePlayers { players: fake_players })
Ok(super::FakePlayers {
players: forced_fake_players.into_iter().chain(filler_players).collect(),
})
}
}

View File

@@ -17,10 +17,11 @@ impl UserData {
cpu_counter: &crate::cubes::CpuListParser,
weapon_lister: &crate::cubes::WeaponListParser,
chooser: &super::TeamChooser,
fake_players: &[crate::persist::config::FakePlayer],
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
let mut fakes = Vec::with_capacity(self.fake_players.len());
let mut fakes = Vec::with_capacity(fake_players.len());
let mut fake_i = real_players.len();
for fake in self.fake_players.iter() {
for fake in fake_players.iter() {
let vehicle = self.resolve_vehicle(&fake.vehicle, factory, weapon_lister, cpu_counter).await?;
let out = (
crate::data::player_data::PlayerData {
@@ -55,6 +56,47 @@ impl UserData {
}
Ok(fakes)
}
pub(super) async fn generate_forced_fake_players_data(
&self,
guid: i64,
real_players: &[super::PlayerLobbyDescriptor],
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
cpu_counter: &crate::cubes::CpuListParser,
weapon_lister: &crate::cubes::WeaponListParser,
chooser: &super::TeamChooser,
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
self.generate_fake_players_data(
guid,
real_players,
factory,
cpu_counter,
weapon_lister,
chooser,
&self.fake_players,
).await
}
pub(super) async fn generate_filler_players_data(
&self,
guid: i64,
real_players: &[super::PlayerLobbyDescriptor],
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
cpu_counter: &crate::cubes::CpuListParser,
weapon_lister: &crate::cubes::WeaponListParser,
chooser: &super::TeamChooser,
count: usize,
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
self.generate_fake_players_data(
guid,
real_players,
factory,
cpu_counter,
weapon_lister,
chooser,
&self.filler_players[0..count],
).await
}
}
#[async_trait::async_trait]

View File

@@ -265,7 +265,7 @@ pub trait LobbyUser {
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 team_chooser(&self, game: &GameDescriptor) -> super::TeamChooser;
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, team_chooser: &super::TeamChooser) -> 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, team_chooser: &super::TeamChooser, missing_players: usize) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
}
pub struct FakePlayers {

View File

@@ -217,11 +217,12 @@ impl QueueHandler {
display_name: x.player.display_name.clone(),
}).collect();
match user.start_game(game_desc, player_descs, factory.as_ref(), &cpu_counter, &weapon_guesser, &team_picker).await {
let missing = users_per_game.saturating_sub(players.len());
match user.start_game(game_desc, player_descs, factory.as_ref(), &cpu_counter, &weapon_guesser, &team_picker, missing).await {
Ok(fakes) => {
let missing = users_per_game.saturating_sub(players.len());
let player_datas = players.iter().map(|x| x.player.clone())
.chain(fakes.players.into_iter().take(missing).map(|(desc, _emu)| desc),)
.chain(fakes.players.into_iter().map(|(desc, _emu)| desc),)
.collect();
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
host: hostname.clone(),

View File

@@ -721,7 +721,7 @@ struct BaseTickInfo {
}
impl BaseTracker {
const DOMINATING_MULT: f32 = 10.0;
const DOMINATING_MULT: f32 = 4.0;
fn new<'a>(bases_iter: impl std::iter::Iterator<Item=&'a u8>, crystals: &[oj_rc_core::cubes::CubeLocationInfo], ba_config: &oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
let mut bases = std::collections::HashMap::new();
@@ -735,19 +735,40 @@ impl BaseTracker {
}
}
fn apply_partial_base_heal(&self, crystals: &[oj_rc_core::cubes::CubeLocationInfo], crystal_health: u32, base_id: u8, base: &BaseInfo, old_index: usize, new_index: usize) -> rlnl::events::HealedCubes {
//let base = self.bases.get(&base_id).unwrap();
let target_crystals = &crystals[old_index..new_index];
for crystal_i in old_index..new_index {
base.crystals_healths[crystal_i].store(u8::MAX, std::sync::atomic::Ordering::Relaxed);
}
rlnl::events::HealedCubes {
healed_machine: base_id as u16,
type_performing_healing: rlnl::types::TargetType::TeamBase,
target_type: rlnl::types::TargetType::TeamBase,
num_healed_cubes: target_crystals.len() as _,
hit_cubes: target_crystals
.iter()
.map(|loc| rlnl::types::HitCubeInfo {
pos: rlnl::types::Byte3 { x: loc.x, y: loc.y, z: loc.z, },
damage: crystal_health as i32,
})
.collect(),
}
}
async fn tick(&self, tick_info: &PointTickInfo, crystals: &[oj_rc_core::cubes::CubeLocationInfo], generic: &crate::matches::GenericGamemodeEngine<BattleArenaLogic>, capture_points_count: usize, crystal_health: u32, ba_config: &oj_rc_core::data::battle_arena_config::BattleArenaData) -> BaseTickInfo {
let multiplier = if let Some(dominant_team) = tick_info.dominating {
if !self.dominating.swap(true, std::sync::atomic::Ordering::Relaxed) {
log::info!("Team {} is now dominating game {}", dominant_team, generic.game_guid());
// FIXME this doesn't seem to trigger the announcement client-side
generic.broadcast(
rlnl::event_code::NetworkEvent::CapturePointNotification,
literustlib::packet::Property::ReliableOrdered,
&rlnl::events::ingame::CapturePointNotification {
notification: rlnl::types::CapturePointNotificationType::Dominating,
id: dominant_team,
defending_team: dominant_team as i8,
attacking_team: (((dominant_team as usize) + 1) % generic.map_config.bases.len()) as i8
id: u8::MAX, // ignored?
attacking_team: dominant_team as i8,
defending_team: -1, // ignored?
//defending_team: (((dominant_team as usize) + 1) % generic.map_config.bases.len()) as i8
},
true
).await;
@@ -795,7 +816,8 @@ impl BaseTracker {
],
}
} else {
let target_crystals = &crystals[old_index..new_index];
self.apply_partial_base_heal(crystals, crystal_health, *base_id, tracked_base, old_index, new_index)
/*let target_crystals = &crystals[old_index..new_index];
for crystal_i in old_index..new_index {
tracked_base.crystals_healths[crystal_i].store(u8::MAX, std::sync::atomic::Ordering::Relaxed);
}
@@ -811,7 +833,7 @@ impl BaseTracker {
damage: crystal_health as i32,
})
.collect(),
}
}*/
};
generic.broadcast(
@@ -974,6 +996,7 @@ impl BattleArenaLogic {
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 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_from(&mut std::io::Cursor::new(&ba_config.base_machine_map), Self::CRYSTAL_ID, (44, 255, 46));
Self {
respawn_full_heal_duration: config.respawn_full_heal_duration,
respawn_heal_duration: config.respawn_heal_duration,
@@ -1147,7 +1170,7 @@ impl BattleArenaLogic {
}
}
async fn do_team_base_stealing(&self, cube_damage: &rlnl::events::ingame::DestroyCubeNoEffect, generic: &crate::matches::GenericGamemodeEngine<Self>, actual_damage_data: impl FnOnce(Vec<rlnl::types::CubeState>) -> crate::matches::RlnlPacket) {
async fn do_team_base_stealing(&self, cube_damage: &rlnl::events::ingame::DestroyCubeNoEffect, generic: &crate::matches::GenericGamemodeEngine<Self>, _actual_damage_data: impl FnOnce(Vec<rlnl::types::CubeState>) -> crate::matches::RlnlPacket) {
let base_id = cube_damage.hit_machine_id as u8;
let mut total_destroyed = 0;
let mut valid_cubes = Vec::with_capacity(cube_damage.hit_cubes.len());
@@ -1178,30 +1201,49 @@ impl BattleArenaLogic {
},
true,
).await;*/
let actual_damage_data = actual_damage_data(valid_cubes);
generic.broadcast(
//let actual_damage_data = actual_damage_data(valid_cubes);
/*generic.broadcast(
actual_damage_data.event,
actual_damage_data.property,
actual_damage_data.data.as_ref(),
true,
).await;
generic.broadcast(
).await;*/
/*generic.broadcast(
rlnl::event_code::NetworkEvent::SyncTeamBaseCubes,
literustlib::packet::Property::ReliableOrdered,
&base.generate_full_base_heal(base_id, self.config.protonium_health as u32, &self.crystals),
true,
).await;
).await;*/
}
if total_destroyed != 0 {
if let Some(team_id) = self.player_tracking.team(cube_damage.shooting_machine_id as u8).await {
if let Some(base) = self.base_tracking.bases.get(&team_id) {
//log::info!("Player {} stole {} crystals", cube_damage.shooting_machine_id, total_destroyed);
base.cube_index.fetch_add(total_destroyed as f32, std::sync::atomic::Ordering::SeqCst);
generic.broadcast(
let total_destroyed_f32 = total_destroyed as f32;
let old_float_index = base.cube_index.fetch_add(total_destroyed_f32, std::sync::atomic::Ordering::SeqCst);
let new_float_index = old_float_index + total_destroyed_f32;
let old_index = (old_float_index.ceil() as usize).clamp(0, self.crystals.len());
let new_index = (new_float_index.ceil() as usize).clamp(0, self.crystals.len());
let payload = self.base_tracking.apply_partial_base_heal(
&self.crystals,
self.config.protonium_health as u32,
team_id,
base,
old_index,
new_index,
);
/*generic.broadcast(
rlnl::event_code::NetworkEvent::SyncTeamBaseCubes,
literustlib::packet::Property::ReliableOrdered,
&base.generate_full_base_heal(team_id, self.config.protonium_health as u32, &self.crystals),
true,
).await;*/
generic.broadcast(
rlnl::event_code::NetworkEvent::SyncTeamBaseCubes,
literustlib::packet::Property::ReliableOrdered,
&payload,
true,
).await;
}
} else {
@@ -1470,7 +1512,7 @@ impl CustomGameLogic for BattleArenaLogic {
}
};
self.do_team_base_stealing(&pseudo, generic, actual_damage_data).await;
false
true
},
rlnl::types::TargetType::EqualizerCrystal => {
self.do_equalizer_damage(&pseudo, generic).await;
@@ -1502,7 +1544,7 @@ impl CustomGameLogic for BattleArenaLogic {
}
};
self.do_team_base_stealing(cube_damage, generic, &actual_damage_data).await;
false
true
},
rlnl::types::TargetType::EqualizerCrystal => {
self.do_equalizer_damage(cube_damage, generic).await;