mod calls;
mod content;
mod events;
mod gameplay;
mod hooks;
mod inventory;
mod pacing;
mod players;
mod points;
mod props;
mod rates;
mod settings;
mod stats;
mod table;
mod ticks;
mod transactions;
mod ui;
mod world;
pub use content::{LiveChange, ServedResource};
pub use hooks::LinkedHook;
pub use inventory::SubmittedRequest;
pub use settings::SettingsRefused;
pub use transactions::{FakeChange, FakeTransaction};
pub use ui::{Drawn, FakeMarker, Input, ShownDialog};
#[cfg(doctest)]
#[doc = include_str!("../../../book/src/testing.md")]
#[expect(dead_code, reason = "the doc comment carries the chapter")]
struct BookTesting;
#[cfg(doctest)]
#[doc = include_str!("../../../book/src/migrations.md")]
#[expect(dead_code, reason = "the doc comment carries the chapter")]
struct BookMigrations;
use std::collections::{HashMap, HashSet};
use std::sync::MutexGuard;
use crate::FnPtr;
use crate::abi::{
CallerFrame, EntityPlace, Fingerprint, HostApi, ItemData, Level, ModHandle, ModInfo,
PlayerInfo, Side, Status,
};
use players::{FakeService, ask, console};
use settings::FakeMigrations;
pub type InitFn = unsafe extern "system" fn(*const HostApi, ModHandle) -> Status;
pub type ReadyFn = unsafe extern "system" fn(*const HostApi, ModHandle);
pub const MOD_ID: &str = "test.fake";
#[must_use]
pub fn address<F: FnPtr>(function: F) -> u64 {
let address: usize = unsafe { core::mem::transmute_copy(&function) };
address as u64
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BaseCode {
Known,
ZonesOnly,
Unknown,
}
#[derive(Clone, Debug)]
pub struct FakeHost {
side: Side,
base: u64,
fingerprint: Fingerprint,
table: crate::abi::BuildTable,
handle: ModHandle,
symbols: HashMap<String, u64>,
systems: HashMap<String, u64>,
constants: HashMap<String, u64>,
field_offsets: HashMap<(String, String), u64>,
type_sizes: HashMap<String, u32>,
type_hashes: HashMap<String, u32>,
event_types: Vec<(&'static str, u32, u32)>,
settings: Option<String>,
other_settings: HashMap<String, String>,
server_synced: Option<String>,
data_dir: String,
log_level: Level,
caller_frame: CallerFrame,
components: HashMap<(u32, u32), (usize, usize)>,
templates: HashMap<u32, [u8; 16]>,
players: Vec<PlayerInfo>,
mods: Vec<ModInfo>,
hosting: bool,
host_key: Option<crate::PlayerKey>,
server_world: u64,
join_missing: Vec<ModInfo>,
replaced: Vec<ModInfo>,
language: u32,
services: Vec<FakeService>,
refused_hooks: HashSet<u64>,
code: (u64, u64),
workshop_recipes: u32,
items: HashMap<u32, ItemData>,
no_inventories: bool,
base_code: BaseCode,
console_answers: HashMap<String, String>,
placed: Vec<(u32, EntityPlace)>,
world_key: Option<String>,
fog: Option<(crate::abi::FogInfo, Vec<u8>)>,
map_markers: Vec<crate::abi::MapMarkerInfo>,
map_frame: Option<crate::abi::MapFrameInfo>,
refused_writes: HashSet<u32>,
static_halves: HashSet<u32>,
net_peers: HashSet<u64>,
icons_ready: bool,
archive: std::collections::BTreeMap<[u32; 4], Vec<u8>>,
resources: HashMap<([u8; 16], u32, u32), Vec<u8>>,
game_texts: HashMap<(u32, u32), &'static str>,
disabled_recipes: HashSet<u32>,
recipe_kinds: HashMap<u32, crate::abi::RecipeKind>,
missing_rates: HashSet<crate::abi::RateId>,
voxels: Vec<world::VoxelGrid>,
zones: world::FakeZones,
extent: Option<[u32; 3]>,
blueprints: HashMap<u32, [u32; 3]>,
props: Vec<props::FakeProp>,
trigger_items: Vec<u32>,
growing_items: Vec<u32>,
no_prop_items: Vec<u32>,
placement_boxes: Vec<(u32, [f32; 3], [f32; 3])>,
building_materials: HashMap<u32, u8>,
}
impl Default for FakeHost {
fn default() -> Self {
Self::new()
}
}
impl FakeHost {
#[must_use]
pub fn new() -> Self {
Self {
side: Side::SERVER,
base: 0x1_4000_0000,
fingerprint: Fingerprint {
guid: [0; 16],
age: 0,
},
table: crate::abi::BuildTable::EXACT,
handle: ModHandle(1),
symbols: HashMap::new(),
systems: HashMap::new(),
constants: HashMap::new(),
field_offsets: HashMap::new(),
type_sizes: HashMap::new(),
type_hashes: HashMap::new(),
event_types: Vec::new(),
settings: None,
other_settings: HashMap::new(),
server_synced: None,
data_dir: "mod-data/test".to_owned(),
log_level: Level::INFO,
caller_frame: CallerFrame::default(),
components: HashMap::new(),
templates: HashMap::new(),
players: Vec::new(),
mods: Vec::new(),
hosting: false,
host_key: None,
server_world: 0,
join_missing: Vec::new(),
replaced: Vec::new(),
language: 0,
services: Vec::new(),
refused_hooks: HashSet::new(),
code: (0, 0),
workshop_recipes: 512,
items: HashMap::new(),
no_inventories: false,
base_code: BaseCode::Known,
console_answers: HashMap::new(),
placed: Vec::new(),
world_key: None,
fog: None,
map_markers: Vec::new(),
map_frame: None,
refused_writes: HashSet::new(),
static_halves: HashSet::new(),
net_peers: HashSet::new(),
icons_ready: false,
archive: std::collections::BTreeMap::new(),
resources: HashMap::new(),
game_texts: HashMap::new(),
disabled_recipes: HashSet::new(),
recipe_kinds: HashMap::new(),
missing_rates: HashSet::new(),
voxels: Vec::new(),
zones: world::FakeZones::default(),
extent: None,
blueprints: HashMap::new(),
props: Vec::new(),
trigger_items: Vec::new(),
growing_items: Vec::new(),
no_prop_items: Vec::new(),
placement_boxes: Vec::new(),
building_materials: HashMap::new(),
}
}
#[must_use]
pub fn handle(mut self, handle: ModHandle) -> Self {
self.handle = handle;
self
}
#[must_use]
pub const fn language(mut self, language: crate::i18n::Language) -> Self {
self.language = language.0;
self
}
#[must_use]
pub fn install(self) -> Installed {
let handle = self.handle;
let turn = table::install(self);
crate::fresh::renew();
Installed {
handle,
_turn: turn,
}
}
}
#[derive(Debug)]
pub struct Installed {
handle: ModHandle,
_turn: MutexGuard<'static, ()>,
}
impl Drop for Installed {
fn drop(&mut self) {
table::uninstall();
}
}
#[expect(
clippy::unused_self,
reason = "the fake's state is process-wide; `self` proves it is installed"
)]
impl Installed {
#[must_use]
pub fn api(&self) -> *const HostApi {
&raw const table::TABLE
}
#[must_use]
pub fn handle(&self) -> ModHandle {
self.handle
}
pub unsafe fn init(&self, init: InitFn) -> Status {
unsafe { init(self.api(), self.handle) }
}
pub unsafe fn ready(&self, ready: ReadyFn) {
table::with((), |s| s.base_limit.armed = true);
unsafe { ready(self.api(), self.handle) }
}
pub unsafe fn start(&self, init: InitFn, ready: ReadyFn) -> Status {
let status = unsafe { self.init(init) };
if status.is_ok() {
unsafe { self.ready(ready) };
}
status
}
#[must_use]
pub fn logs(&self) -> Vec<(Level, String)> {
table::with(Vec::new(), |s| s.logs.clone())
}
#[must_use]
pub fn faults(&self) -> Vec<String> {
table::with(Vec::new(), |s| s.faults.clone())
}
}