use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use strum::EnumCount;
use crate::map::MapThing;
use crate::math::{Angle, Fixed, ANG90, FRACBITS, FRACUNIT};
use crate::specials::SpecialThinker;
use crate::types::{AmmoType, ArmorType, Card, WeaponType};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Pose {
pub x: Fixed,
pub y: Fixed,
pub z: Fixed,
pub angle: Angle,
}
impl Pose {
#[inline]
pub fn on_floor(x: Fixed, y: Fixed, angle: Angle) -> Self {
Self { x, y, z: 0, angle }
}
#[inline]
pub fn from_map_thing(thing: &MapThing) -> Self {
let x = (thing.x as Fixed) << FRACBITS;
let y = (thing.y as Fixed) << FRACBITS;
let deg = (thing.angle as i32).rem_euclid(360) as u64;
let angle = (deg * ANG90 as u64 / 90) as Angle;
Self { x, y, z: 0, angle }
}
}
pub trait HasPose {
fn pose(&self) -> Pose;
}
impl HasPose for Pose {
#[inline]
fn pose(&self) -> Pose {
*self
}
}
impl HasPose for Entity {
#[inline]
fn pose(&self) -> Pose {
Pose {
x: self.x,
y: self.y,
z: self.z,
angle: self.angle,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct EntityId(pub u32);
pub use crate::game_data::EntityType;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct PeerId(pub u32);
#[derive(Clone, Debug)]
pub struct SectorState {
pub floor_height: Fixed,
pub ceiling_height: Fixed,
pub light_level: i16,
pub special: i16,
}
#[derive(Clone, Debug)]
pub struct PlayerState {
pub armor_points: i32,
pub armor_type: ArmorType,
pub ammo: [i32; AmmoType::COUNT],
pub max_ammo: [i32; AmmoType::COUNT],
pub weapon_owned: [bool; WeaponType::COUNT],
pub ready_weapon: WeaponType,
pub pending_weapon: WeaponType,
pub cards: [bool; Card::COUNT],
pub use_down: bool,
pub attack_down: bool,
pub refire_count: i32,
pub kill_count: i32,
pub item_count: i32,
pub secret_count: i32,
pub psp_state: crate::game_data::StateNum,
pub psp_tics: i32,
pub psp_sx: Fixed,
pub psp_sy: Fixed,
pub flash_state: crate::game_data::StateNum,
pub flash_tics: i32,
pub bob: Fixed,
}
impl Default for PlayerState {
fn default() -> Self {
Self::new()
}
}
impl PlayerState {
pub fn new() -> Self {
Self {
armor_points: 0,
armor_type: ArmorType::None,
ammo: [50, 0, 0, 0], max_ammo: [200, 50, 300, 50],
weapon_owned: [true, true, false, false, false, false, false, false, false],
ready_weapon: WeaponType::Pistol,
pending_weapon: WeaponType::Pistol,
cards: [false; Card::COUNT],
use_down: false,
attack_down: false,
refire_count: 0,
kill_count: 0,
item_count: 0,
secret_count: 0,
psp_state: crate::game_data::S_PISTOL,
psp_tics: 1,
psp_sx: FRACUNIT, psp_sy: 32 * FRACUNIT, flash_state: crate::game_data::StateNum::NULL,
flash_tics: 0,
bob: 0,
}
}
pub fn give_ammo(&mut self, ammo: AmmoType, amount: i32) -> bool {
let i = ammo as usize;
let (Some(cur), Some(&max)) = (self.ammo.get_mut(i), self.max_ammo.get(i)) else {
return false;
};
if *cur >= max {
return false;
}
*cur = cur.saturating_add(amount).min(max);
true
}
pub fn grant_weapon(&mut self, w: WeaponType) {
if let Some(slot) = self.weapon_owned.get_mut(w as usize) {
*slot = true;
}
}
pub fn grant_card(&mut self, c: Card) {
if let Some(slot) = self.cards.get_mut(c as usize) {
*slot = true;
}
}
pub fn double_max_ammo(&mut self) {
for slot in &mut self.max_ammo {
*slot = slot.saturating_mul(2);
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum LevelExit {
#[default]
None,
Normal,
Secret,
}
#[inline]
pub fn exit_kind(special: i16) -> LevelExit {
use crate::map::line_special::EXIT_SECRET;
if special == EXIT_SECRET {
LevelExit::Secret
} else {
LevelExit::Normal
}
}
pub struct World {
entities: Vec<Option<Entity>>,
id_to_slot: BTreeMap<EntityId, usize>,
free_slots: Vec<usize>,
next_id: u32,
pub sectors: Vec<SectorState>,
pub tick: u32,
pub level_exit: LevelExit,
controllers: BTreeMap<PeerId, EntityId>,
player_states: BTreeMap<EntityId, PlayerState>,
pub specials: Vec<SpecialThinker>,
id_scratch: Vec<EntityId>,
prng_index: u8,
}
impl World {
pub fn new(sectors: Vec<SectorState>) -> Self {
Self {
entities: Vec::new(),
id_to_slot: BTreeMap::new(),
free_slots: Vec::new(),
next_id: 1,
sectors,
tick: 0,
level_exit: LevelExit::None,
controllers: BTreeMap::new(),
player_states: BTreeMap::new(),
specials: Vec::new(),
id_scratch: Vec::new(),
prng_index: 0,
}
}
pub fn set_controller(&mut self, peer: PeerId, entity: EntityId) {
self.controllers.insert(peer, entity);
self.player_states.entry(entity).or_default();
}
pub fn player_state(&self, entity: EntityId) -> Option<&PlayerState> {
self.player_states.get(&entity)
}
pub fn player_state_mut(&mut self, entity: EntityId) -> Option<&mut PlayerState> {
self.player_states.get_mut(&entity)
}
pub fn controlled_by(&self, peer: PeerId) -> Option<EntityId> {
self.controllers.get(&peer).copied()
}
pub fn is_controlled(&self, entity: EntityId) -> bool {
self.controllers.values().any(|&eid| eid == entity)
}
pub fn controlled_entities(&self) -> impl Iterator<Item = EntityId> + '_ {
self.controllers.values().copied()
}
pub fn spawn(&mut self, mut entity: Entity) -> EntityId {
let id = EntityId(self.next_id);
self.next_id = self.next_id.wrapping_add(1);
entity.id = id;
let reused = self
.free_slots
.pop()
.filter(|&s| s < self.entities.len());
let slot = match reused {
Some(s) => {
if let Some(cell) = self.entities.get_mut(s) {
*cell = Some(entity);
}
s
}
None => {
let slot = self.entities.len();
self.entities.push(Some(entity));
slot
}
};
self.id_to_slot.insert(id, slot);
id
}
pub fn remove(&mut self, id: EntityId) {
if let Some(slot) = self.id_to_slot.remove(&id)
&& let Some(cell) = self.entities.get_mut(slot)
{
*cell = None;
self.free_slots.push(slot);
}
}
pub fn get(&self, id: EntityId) -> Option<&Entity> {
let &slot = self.id_to_slot.get(&id)?;
self.entities.get(slot)?.as_ref()
}
pub fn get_mut(&mut self, id: EntityId) -> Option<&mut Entity> {
let &slot = self.id_to_slot.get(&id)?;
self.entities.get_mut(slot)?.as_mut()
}
pub fn iter(&self) -> impl Iterator<Item = &Entity> {
self.entities.iter().filter_map(|s| s.as_ref())
}
pub fn take_entity_ids(&mut self) -> Vec<EntityId> {
self.id_scratch.clear();
self.id_scratch.extend(
self.entities.iter().filter_map(|s| s.as_ref().map(|e| e.id))
);
core::mem::take(&mut self.id_scratch)
}
pub fn return_id_scratch(&mut self, buf: Vec<EntityId>) {
self.id_scratch = buf;
}
pub fn entity_count(&self) -> usize {
self.id_to_slot.len()
}
pub fn p_random(&mut self) -> i32 {
self.prng_index = self.prng_index.wrapping_add(1);
#[allow(clippy::indexing_slicing)]
let v = RNDTABLE[self.prng_index as usize];
v as i32
}
}
static RNDTABLE: [u8; 256] = [
0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, 74, 21,
211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36, 95, 110, 85, 48,
212, 140, 211, 249, 22, 79, 200, 50, 28, 188, 52, 140, 202, 120, 68, 145,
62, 70, 184, 190, 91, 197, 152, 224, 149, 104, 25, 178, 252, 182, 202, 182,
141, 197, 4, 81, 181, 242, 145, 42, 39, 227, 156, 198, 225, 193, 219, 93,
122, 175, 249, 0, 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135,
2, 235, 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113,
94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75, 136, 156,
11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196, 135, 106, 63, 197,
195, 86, 96, 203, 113, 101, 170, 247, 181, 113, 80, 250, 108, 7, 255, 237,
129, 226, 79, 107, 112, 166, 103, 241, 24, 223, 239, 120, 198, 58, 60, 82,
128, 3, 184, 66, 143, 224, 145, 224, 81, 206, 163, 45, 63, 90, 168, 114,
59, 33, 159, 95, 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14,
109, 226, 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 204, 51, 233,
231, 243, 213, 187, 128, 173, 85, 203, 197, 235, 60, 129, 148, 64, 107, 171,
55, 82, 100, 186, 249, 206, 162, 227, 159, 28, 168, 174, 122, 110, 187, 213,
];
#[derive(Clone, Debug)]
pub struct Entity {
pub id: EntityId,
pub entity_type: EntityType,
pub x: Fixed,
pub y: Fixed,
pub z: Fixed,
pub angle: Angle,
pub floor_z: Fixed,
pub ceiling_z: Fixed,
pub momx: Fixed,
pub momy: Fixed,
pub momz: Fixed,
pub radius: Fixed,
pub height: Fixed,
pub health: i32,
pub flags: crate::game_data::MobjFlags,
pub sprite: crate::game_data::SpriteNum,
pub frame: i32,
pub state: crate::game_data::StateNum,
pub tics: i32,
pub target: Option<EntityId>,
pub reaction_time: i32,
pub move_dir: u8,
pub move_count: i32,
}
impl Default for Entity {
fn default() -> Self {
Self {
id: EntityId(0),
entity_type: EntityType(0),
x: 0, y: 0, z: 0, angle: 0,
floor_z: 0, ceiling_z: 0,
momx: 0, momy: 0, momz: 0,
radius: 0,
height: 0,
health: 0,
flags: crate::game_data::MobjFlags::empty(),
sprite: crate::game_data::SpriteNum(0), frame: 0,
state: crate::game_data::StateNum::NULL, tics: -1,
target: None,
reaction_time: 0,
move_dir: 0,
move_count: 0,
}
}
}