Keep bots inside the 18 player slots instead of colliding with corpses

A bot took the next unused client id with no upper bound, and the roster
only capped bots at MAX_HOST_BOTS (20). The lobby's Enemy and Friendly
Bots rules plus a `bot add`, or two `bot add` commands, could therefore
make more than 17 bots beside the host. Client ids are also entity
numbers, and the corpse ring starts at entity 18
(PLAYER_CORPSE_ENTITY_BASE), so bots 18 and up shared an entity number
with a corpse: the net layer rejected the snapshot entity as a
centity-number gap, and the end-of-match client data overran its 18 rows
(setclientmatchdata IndexOutOfRange).

Bots now claim ids 1..17 only, wrapping around to reuse freed ids, and
adding stops when every slot is taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JohnM
2026-10-10 00:12:10 +08:00
parent 91217cd0ac
commit d934e6ea33
+7 -9
View File
@@ -5,6 +5,8 @@ use crate::controller::HostController;
pub const MAX_HOST_BOTS: u32 = 20;
const MAX_CLIENT_SLOTS: u32 = playerstate_iw4::PLAYER_CORPSE_ENTITY_BASE as u32;
const BOT_NAMES: [&str; 52] = [
"contextrot",
"t.me/contextrot",
@@ -250,16 +252,12 @@ impl BotRoster {
}
fn claim_id(&mut self, taken: &[ClientId]) -> Option<ClientId> {
// Only `bots + taken` ids are spoken for, so one candidate more than
// that always turns up a free one.
let candidates = self
.bots
.len()
.saturating_add(taken.len())
.saturating_add(1);
for _ in 0..candidates {
for _ in 1..MAX_CLIENT_SLOTS {
if !(1..MAX_CLIENT_SLOTS).contains(&self.next_client) {
self.next_client = 1;
}
let id = ClientId(self.next_client);
self.next_client = self.next_client.wrapping_add(1).max(1);
self.next_client += 1;
if !taken.contains(&id) && !self.is_bot(id) {
return Some(id);
}