let the bot play cards
every four to eleven seconds the bot draws a card from the deck we gave it and puts it down on its own side, on the lane of whichever of its towers stands furthest forward. the card goes through the same summon factory the player's cards use, so it lands in the server simulation and reaches the player inside the next snapshot rather than through any special path. placement comes from the bot's own tower positions rather than from the tilemap, so the session does not need the map threaded into it.
This commit is contained in:
parent
3bb923cd17
commit
3f0cf3d1d4
3 changed files with 78 additions and 3 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
pub const SNAPSHOT_INTERVAL_TICKS: i32 = 20;
|
pub const SNAPSHOT_INTERVAL_TICKS: i32 = 20;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use logic::battle::{LogicGameMode, LogicGameObjectEntry, BATTLE_TICKS_PER_SECOND};
|
use logic::battle::{LogicGameMode, LogicGameObjectEntry, LogicVector2, BATTLE_TICKS_PER_SECOND};
|
||||||
use logic::battle::{verify_snapshot, LogicBattleEvent};
|
use logic::battle::{verify_snapshot, LogicBattleEvent};
|
||||||
use logic::SectorStateMessage;
|
use logic::SectorStateMessage;
|
||||||
use logic::{BattleEventMessage, LogicDataRef, LogicRandom};
|
use logic::{BattleEventMessage, LogicDataRef, LogicRandom};
|
||||||
|
|
@ -18,6 +18,8 @@ pub struct BattleSession {
|
||||||
taunts: Vec<LogicDataRef>,
|
taunts: Vec<LogicDataRef>,
|
||||||
next_bot_emote: i32,
|
next_bot_emote: i32,
|
||||||
next_snapshot: i32,
|
next_snapshot: i32,
|
||||||
|
next_bot_play: i32,
|
||||||
|
pending_bot_play: Option<(LogicDataRef, LogicVector2, i32)>,
|
||||||
}
|
}
|
||||||
impl BattleSession {
|
impl BattleSession {
|
||||||
pub fn new(mode: LogicGameMode, taunts: Vec<LogicDataRef>) -> Self {
|
pub fn new(mode: LogicGameMode, taunts: Vec<LogicDataRef>) -> Self {
|
||||||
|
|
@ -33,8 +35,53 @@ impl BattleSession {
|
||||||
taunts,
|
taunts,
|
||||||
next_bot_emote,
|
next_bot_emote,
|
||||||
next_snapshot: SNAPSHOT_INTERVAL_TICKS,
|
next_snapshot: SNAPSHOT_INTERVAL_TICKS,
|
||||||
|
next_bot_play: 0,
|
||||||
|
pending_bot_play: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn take_bot_play(&mut self) -> Option<(LogicDataRef, LogicVector2, i32)> {
|
||||||
|
self.pending_bot_play.take()
|
||||||
|
}
|
||||||
|
pub fn bot_play(&mut self) -> Option<(LogicDataRef, LogicVector2, i32)> {
|
||||||
|
let deck = self.mode.battle.decks.get(1)?.as_ref()?;
|
||||||
|
let filled: Vec<LogicDataRef> = deck
|
||||||
|
.slots
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|spell| spell.data.clone())
|
||||||
|
.collect();
|
||||||
|
if filled.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let card = filled
|
||||||
|
.get(self.random.next(filled.len() as i32).max(0) as usize)?
|
||||||
|
.clone();
|
||||||
|
let towers: Vec<(i32, i32)> = self
|
||||||
|
.mode
|
||||||
|
.battle
|
||||||
|
.objects
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.filter(|entry| entry.owner_index() == crate::bot::BOT_OWNER_INDEX)
|
||||||
|
.map(|entry| entry.position())
|
||||||
|
.collect();
|
||||||
|
let leader = self.mode.battle.leader(crate::bot::BOT_OWNER_INDEX as usize)?;
|
||||||
|
let (king_x, king_y) = leader.position();
|
||||||
|
let ahead = towers
|
||||||
|
.iter()
|
||||||
|
.map(|(_, y)| *y)
|
||||||
|
.fold(king_y, |best, y| if (y - king_y).abs() > (best - king_y).abs() { y } else { best });
|
||||||
|
let lane_x = towers
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, y)| *y == ahead)
|
||||||
|
.map(|(x, _)| *x)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let x = lane_x
|
||||||
|
.get(self.random.next(lane_x.len().max(1) as i32).max(0) as usize)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(king_x);
|
||||||
|
Some((card, LogicVector2::new(x, ahead), self.next_instance()))
|
||||||
|
}
|
||||||
fn snapshot_message(&mut self) -> Option<WireMessage> {
|
fn snapshot_message(&mut self) -> Option<WireMessage> {
|
||||||
let snapshot = self.mode.snapshot().ok()?;
|
let snapshot = self.mode.snapshot().ok()?;
|
||||||
let report = verify_snapshot(&snapshot);
|
let report = verify_snapshot(&snapshot);
|
||||||
|
|
@ -117,6 +164,14 @@ impl BattleSession {
|
||||||
self.outbound.push(message);
|
self.outbound.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.tick >= self.next_bot_play {
|
||||||
|
let span = crate::bot::BOT_PLAY_MAX_SECONDS - crate::bot::BOT_PLAY_MIN_SECONDS;
|
||||||
|
let wait = crate::bot::BOT_PLAY_MIN_SECONDS + self.random.next(span.max(1));
|
||||||
|
self.next_bot_play = self.tick + wait * BATTLE_TICKS_PER_SECOND;
|
||||||
|
if let Some((card, position, instance)) = self.bot_play() {
|
||||||
|
self.pending_bot_play = Some((card, position, instance));
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.tick >= self.next_bot_emote {
|
if self.tick >= self.next_bot_emote {
|
||||||
self.next_bot_emote = Self::roll_emote_tick(&mut self.random, self.tick);
|
self.next_bot_emote = Self::roll_emote_tick(&mut self.random, self.tick);
|
||||||
if let Some(event) = self.bot_emote() {
|
if let Some(event) = self.bot_emote() {
|
||||||
|
|
@ -193,12 +248,24 @@ impl BattleRegistry {
|
||||||
pub async fn finish(&self, account: AccountRef) -> Option<BattleSession> {
|
pub async fn finish(&self, account: AccountRef) -> Option<BattleSession> {
|
||||||
self.sessions.lock().await.remove(&account)
|
self.sessions.lock().await.remove(&account)
|
||||||
}
|
}
|
||||||
pub async fn tick(&self, account: AccountRef) -> Vec<WireMessage> {
|
pub async fn tick<F>(&self, account: AccountRef, summon: F) -> Vec<WireMessage>
|
||||||
|
where
|
||||||
|
F: Fn(&LogicDataRef, LogicVector2, i32, i32) -> Vec<LogicGameObjectEntry>,
|
||||||
|
{
|
||||||
let mut sessions = self.sessions.lock().await;
|
let mut sessions = self.sessions.lock().await;
|
||||||
let Some(session) = sessions.get_mut(&account) else {
|
let Some(session) = sessions.get_mut(&account) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
session.advance_to_now();
|
session.advance_to_now();
|
||||||
|
if let Some((card, position, instance)) = session.take_bot_play() {
|
||||||
|
let entries = summon(&card, position, crate::bot::BOT_OWNER_INDEX, instance);
|
||||||
|
if !entries.is_empty() {
|
||||||
|
tracing::debug!(card = %card, count = entries.len(), "the bot played a card");
|
||||||
|
for entry in entries {
|
||||||
|
session.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
session.take_outbound()
|
session.take_outbound()
|
||||||
}
|
}
|
||||||
pub async fn answer_emote(&self, account: AccountRef) {
|
pub async fn answer_emote(&self, account: AccountRef) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ pub const BOT_NAME: &str = "Bot";
|
||||||
pub const TAUNT_MENU_COLUMN: &str = "TauntMenu";
|
pub const TAUNT_MENU_COLUMN: &str = "TauntMenu";
|
||||||
pub const BOT_EMOTE_MIN_SECONDS: i32 = 12;
|
pub const BOT_EMOTE_MIN_SECONDS: i32 = 12;
|
||||||
pub const BOT_EMOTE_MAX_SECONDS: i32 = 30;
|
pub const BOT_EMOTE_MAX_SECONDS: i32 = 30;
|
||||||
|
pub const BOT_PLAY_MIN_SECONDS: i32 = 4;
|
||||||
|
pub const BOT_PLAY_MAX_SECONDS: i32 = 11;
|
||||||
|
pub const BOT_OWNER_INDEX: i32 = 1;
|
||||||
pub const DECK_SPELLS_COLUMN: &str = "Spells";
|
pub const DECK_SPELLS_COLUMN: &str = "Spells";
|
||||||
pub const DECK_LEVEL_COLUMN: &str = "SpellLevel";
|
pub const DECK_LEVEL_COLUMN: &str = "SpellLevel";
|
||||||
pub fn menu_taunts() -> Vec<LogicDataRef> {
|
pub fn menu_taunts() -> Vec<LogicDataRef> {
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,12 @@ impl GameApi for GameService {
|
||||||
Ok(replies)
|
Ok(replies)
|
||||||
}
|
}
|
||||||
async fn battle_tick(&self, account: AccountRef) -> RpcResult<Vec<WireMessage>> {
|
async fn battle_tick(&self, account: AccountRef) -> RpcResult<Vec<WireMessage>> {
|
||||||
Ok(self.running_battles.tick(account).await)
|
Ok(self
|
||||||
|
.running_battles
|
||||||
|
.tick(account, |card, position, owner, instance| {
|
||||||
|
self.battles.summon(card, position, owner, 0, instance)
|
||||||
|
})
|
||||||
|
.await)
|
||||||
}
|
}
|
||||||
async fn battle_event(&self, account: AccountRef, payload: Vec<u8>) -> RpcResult<()> {
|
async fn battle_event(&self, account: AccountRef, payload: Vec<u8>) -> RpcResult<()> {
|
||||||
let Ok(sent) = SendBattleEventMessage::from_bytes(&payload) else {
|
let Ok(sent) = SendBattleEventMessage::from_bytes(&payload) else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue