use game_service::battle::BattleBuilder; use game_service::battle_session::{BattleSession, COMMAND_DELAY_TICKS, DEPLOY_BIRTH_TICKS}; use logic::battle::LogicVector2; 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 card_lands_after_fire() { let root = assets(); let tables = LogicDataTables::load_from_dir(&root).expect("tables"); LogicDataTables::install(Arc::new(tables)); let builder = BattleBuilder::new(&root); let 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 mut session = BattleSession::new(mode, Vec::new()); let at_tick = 0; let spell = LogicDataRef::spell("Knight"); let entries = builder.summon(&spell, LogicVector2::new(14500, 23500), 0, 0, 100); assert!(!entries.is_empty(), "the knight spell summons nothing"); let before = session.object_count(); let born = session.queue(at_tick, LogicVector2::new(14500, 23500), 0, 3, 0, entries); assert_eq!( born, at_tick + DEPLOY_BIRTH_TICKS, "the birth tick must be one past the firing tick" ); assert_eq!( DEPLOY_BIRTH_TICKS, COMMAND_DELAY_TICKS + 1, "the command still fires at age twenty; only the label the troops are first hashed under moves" ); session.advance_to(born - 1); assert_eq!( session.object_count(), before, "the troops appeared on the firing tick, a tick before the client hashes them" ); session.advance_to(born); assert!( session.object_count() > before, "the troops never arrived on the birth tick" ); } #[test] fn state_request_waits_in_flight() { let root = assets(); let tables = LogicDataTables::load_from_dir(&root).expect("tables"); LogicDataTables::install(Arc::new(tables)); let builder = BattleBuilder::new(&root); let 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 mut session = BattleSession::new(mode, Vec::new()); assert!( session.state_on_request().is_some(), "with nothing in the air a request must be answered at once" ); let first = session.queue( 0, LogicVector2::new(14500, 23500), 0, 3, 0, builder.summon( &LogicDataRef::spell("Archer"), LogicVector2::new(14500, 23500), 0, 0, 100, ), ); session.advance_to(15); let second = session.queue( 15, LogicVector2::new(14000, 23500), 0, 3, 1, builder.summon( &LogicDataRef::spell("Bomber"), LogicVector2::new(14000, 23500), 0, 0, 100, ), ); assert!(second > first, "second lands after first"); let mut refused = Vec::new(); for tick in 16..(second + 4) { if session.state_on_request().is_none() { refused.push(session.tick()); } session.advance_to(tick); } for tick in &refused { let near_a_firing_tick = [first, second] .iter() .any(|fires_at| tick > fires_at && tick <= &(fires_at + 2)); assert!( near_a_firing_tick, "t={tick}: a state was refused away from any firing tick ({first}, {second}); while a full update is pending that silently eats the player's cards" ); } assert!( !refused.is_empty(), "the firing ticks were never protected at all" ); session.advance_to(second + 1); let mut paid = false; for _ in 0..8 { if session.take_outbound().into_iter().count() > 0 { paid = true; break; } session.advance_to(session.tick() + 1); } assert!(paid, "owed state never sent"); }