mod content;
mod events;
mod gameplay;
mod hooks;
mod inventory;
mod players;
mod points;
mod rates;
mod settings;
mod stats;
mod table;
mod ui;
mod world;
pub use content::ServedResource;
pub use hooks::LinkedHook;
pub use inventory::SubmittedRequest;
pub use settings::SettingsRefused;
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, 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,
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,
console_answers: HashMap<String, String>,
placed: Vec<(u32, EntityPlace)>,
world_key: Option<String>,
refused_writes: 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>,
}
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,
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,
console_answers: HashMap::new(),
placed: Vec::new(),
world_key: None,
refused_writes: 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(),
}
}
#[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) {
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())
}
}