use game_service::battle::BattleBuilder; use logic::battle::{LogicComponent, LogicObjectBody, LogicVector2, COMPONENT_COMBAT}; use logic::data::{table, LogicDataRef, LogicDataTables}; use logic::model::LogicClientAvatar; use std::path::Path; use std::sync::Arc; use titan::LogicLong; fn assets() -> std::path::PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../assets") } fn avatar(low: i32) -> LogicClientAvatar { let account = LogicLong::new(0, low); LogicClientAvatar { avatar_id: account, account_id: account, home_id: account, name_change_state: -1, ..LogicClientAvatar::default() } } #[test] fn a_ranged_shooter_updates_twice_on_the_tick_it_fires() { let root = assets(); let tables = LogicDataTables::load_from_dir(&root).expect("tables"); LogicDataTables::install(Arc::new(tables)); let builder = BattleBuilder::new(&root); let mut mode = builder .build( LogicDataRef::by_name(table::LOCATIONS, "PvP_goblin"), LogicDataRef::None, LogicDataRef::by_name(table::ARENAS, "Arena_T"), vec![avatar(5), avatar(0)], [None, None], 1, ) .expect("battle"); let spell = LogicDataRef::spell("Archer"); for entry in builder.summon(&spell, LogicVector2::new(14500, 23500), 0, 0, 100) { mode.battle.objects.push(entry); } const ARCHER_LOAD_TIME: i32 = 1000; let load_timer_of = |mode: &logic::battle::LogicGameMode, id: i32| -> Option { mode.battle.objects.objects.iter().find_map(|entry| { if entry.global_id.0?.instance_id != id { return None; } match entry.components.get(COMPONENT_COMBAT)? { Some(LogicComponent::Combat(combat)) => Some(combat.load_timer), _ => None, } }) }; let archer_ref = mode .battle .objects .objects .iter() .find(|entry| entry.data.name() == "Archer") .map(|entry| entry.global_id) .expect("the archer is on the board"); let archer = archer_ref .0 .expect("the archer has a global id") .instance_id; let mut shots = 0usize; let mut seen: Vec = Vec::new(); for tick in 0..(20 * 20) { mode.battle.tick(tick); let fresh: Vec<_> = mode .battle .objects .objects .iter() .filter_map(|entry| match &entry.body { LogicObjectBody::Projectile(shot) if shot.source == archer_ref => { Some(entry.global_id) } _ => None, }) .filter(|id| !seen.contains(id)) .collect(); for id in &fresh { seen.push(*id); } if fresh.is_empty() { continue; } shots += 1; let load_timer = load_timer_of(&mode, archer).expect("the archer has a combat component"); assert_eq!( load_timer, ARCHER_LOAD_TIME - logic::battle::TICK_MILLISECONDS, "t={tick}: the shooter did not take its second update on the firing tick" ); } assert!( shots > 1, "the archer fired {shots} times, too few to prove anything" ); }