1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Players' interactions with entities.
//!
//! A mod's say before the game opens a chest, moves or sorts its items, lends
//! its stock to crafting, lets a piece be dismantled, or flips its Quick Stash
//! or upgrade switch.
//!
//! The platform asks the filters in the server's world only, on the thread of
//! the system that checks the interaction, and turns a refusal into the
//! game's own refusal, so nothing moves and nothing is lost.
//!
//! # Examples
//!
//! ```
//! use enshwick::interactions::Kind;
//!
//! fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
//! const LOCKED_CHEST: enshwick::ItemId = enshwick::ItemId::new(0x5e01);
//! // Nobody opens or empties a locked chest.
//! load.on_interaction(|i| !(i.item == Some(LOCKED_CHEST) && matches!(i.kind, Kind::Open | Kind::Move)))
//! }
//! ```
use core::ffi::c_void;
use crate::abi::Interaction as RawInteraction;
use crate::{Entity, ItemId};
use crate::{Load, Off};
/// What a player does with the target.
///
/// # Examples
///
/// See the [module](self).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Kind {
/// The game offers to open it.
Open,
/// Items move out of or into it, or out of a slot it holds.
Move,
/// Its inventory is sorted.
Sort,
/// It lends its stock to crafting or building nearby; asked without the
/// target's item or position.
Stock,
/// The cursor offers to pick it up or tear it down.
Dismantle,
/// Its Quick Stash switch is flipped.
QuickStash,
/// It is upgraded into another piece.
Upgrade,
}
/// An interaction the game is about to allow, from [`Load::on_interaction`].
///
/// # Examples
///
/// See the [module](self).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Interaction {
/// What the player does.
pub kind: Kind,
/// The player's character.
pub player: Entity,
/// The entity acted on.
pub target: Entity,
/// The item the target was placed from, when the platform could read it.
pub item: Option<ItemId>,
/// Where the target stands, in metres, when the platform could read it.
pub position: Option<[f64; 3]>,
}
unsafe extern "system" fn run_filter<F>(call: *const RawInteraction, user: *mut c_void) -> u8
where
F: Fn(&Interaction) -> bool,
{
// SAFETY: the loader passes a valid call for the duration of this one.
let Some(call) = (unsafe { call.as_ref() }) else {
return 1;
};
let kind = match call.kind {
RawInteraction::OPEN => Kind::Open,
RawInteraction::MOVE => Kind::Move,
RawInteraction::SORT => Kind::Sort,
RawInteraction::STOCK => Kind::Stock,
RawInteraction::DISMANTLE => Kind::Dismantle,
RawInteraction::QUICK_STASH => Kind::QuickStash,
RawInteraction::UPGRADE => Kind::Upgrade,
_ => return 1,
};
if call.player.is_none() || call.target.is_none() {
return 1;
}
let known = call.known != 0;
let interaction = Interaction {
kind,
player: call.player,
target: call.target,
item: known.then_some(call.item),
position: known.then_some(call.position),
};
// SAFETY: `user` is the leaked `F` this function was registered with.
let filter = unsafe { &*user.cast::<F>() };
u8::from(crate::host::guard(
"panic in an interaction filter",
|| true,
|| filter(&interaction),
))
}
impl Load<'_> {
/// Runs `filter` before the game allows a player's interaction with an
/// entity; returning `false` refuses it the game's own way. It runs in
/// the server's world only, in the dedicated server or a hosting client,
/// on the thread of the system that checks the interaction: keep it quick
/// and never block.
///
/// The game asks whether to offer an interaction for every entity in
/// each player's reach on every frame, so `filter` runs many times a
/// frame: it must not allocate, lock or log. Moves, sorts, stock draws,
/// dismantling and upgrades ask once per action. An interaction without
/// a player's character or without a target is allowed unasked.
///
/// # Errors
///
/// [`Off`] after init or on a build that lacks a piece.
///
/// # Examples
///
/// See the [module](self).
pub fn on_interaction<F>(&mut self, filter: F) -> Result<(), Off>
where
F: Fn(&Interaction) -> bool + Send + Sync + 'static,
{
let host = self.host();
crate::load::hand_over(
filter,
|user| (host.api().interaction_filter)(host.handle(), run_filter::<F>, user),
"interaction filter",
)
}
}