#[cfg(not(feature = "samp-only"))]
use crate::macros::sdk_warn;
use crate::runtime::Runtime;
use samp_sdk::raw::types::{AMX, AMX_NATIVE_INFO};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::component::ICore;
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::events::{PawnEventHandler, PawnEventHandlerVTable};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::server::{
IPawnScript, PAWN_COMPONENT_UID, ServerComponentList, add_pawn_event_handler,
get_amx_from_script, get_amx_functions, get_pawn_event_dispatcher, query_component,
remove_pawn_event_handler,
};
#[cfg(not(feature = "samp-only"))]
use samp_sdk::omp::timers::{
ITimer, TimerHandlerVTable, TimerTimeOutHandler, create_repeating_timer, kill_timer,
query_timers_component,
};
#[cfg(not(feature = "samp-only"))]
static PAWN_HANDLER_VTABLE: PawnEventHandlerVTable = PawnEventHandlerVTable {
on_amx_load: pawn_on_amx_load,
on_amx_unload: pawn_on_amx_unload,
};
#[cfg(not(feature = "samp-only"))]
static TICK_HANDLER_VTABLE: TimerHandlerVTable = TimerHandlerVTable {
timeout: tick_handler_timeout,
free: tick_handler_free,
};
#[cfg(not(feature = "samp-only"))]
#[inline]
fn inner_tick_timeout() {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
tick(crate::plugin::TickSource::OmpTimer);
}));
}
#[cfg(all(not(feature = "samp-only"), not(target_env = "msvc")))]
unsafe extern "C" fn tick_handler_timeout(_handler: *mut TimerTimeOutHandler, _timer: *mut ITimer) {
inner_tick_timeout();
}
#[cfg(all(not(feature = "samp-only"), target_env = "msvc"))]
unsafe extern "thiscall" fn tick_handler_timeout(
_handler: *mut TimerTimeOutHandler,
_timer: *mut ITimer,
) {
inner_tick_timeout();
}
#[cfg(all(not(feature = "samp-only"), not(target_env = "msvc")))]
unsafe extern "C" fn tick_handler_free(handler: *mut TimerTimeOutHandler, _timer: *mut ITimer) {
if !handler.is_null() {
let _ = unsafe { Box::from_raw(handler) };
}
}
#[cfg(all(not(feature = "samp-only"), target_env = "msvc"))]
unsafe extern "thiscall" fn tick_handler_free(
handler: *mut TimerTimeOutHandler,
_timer: *mut ITimer,
) {
if !handler.is_null() {
let _ = unsafe { Box::from_raw(handler) };
}
}
#[cfg(not(feature = "samp-only"))]
fn inner_amx_load(script: *mut IPawnScript) {
let amx_ptr = unsafe { get_amx_from_script(script) };
if amx_ptr.is_null() {
return;
}
let rt = Runtime::get();
if rt.omp_has_amx_exports() {
let natives = rt.omp_natives();
amx_load(amx_ptr, natives);
} else {
rt.enqueue_pending_amx(amx_ptr);
}
}
#[cfg(not(feature = "samp-only"))]
fn inner_amx_unload(script: *mut IPawnScript) {
let amx_ptr = unsafe { get_amx_from_script(script) };
if !amx_ptr.is_null() {
amx_unload(amx_ptr);
}
}
#[cfg(all(not(feature = "samp-only"), not(target_env = "msvc")))]
unsafe extern "C" fn pawn_on_amx_load(_this: *mut PawnEventHandler, script: *mut IPawnScript) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| inner_amx_load(script)));
}
#[cfg(all(not(feature = "samp-only"), target_env = "msvc"))]
unsafe extern "thiscall" fn pawn_on_amx_load(
_this: *mut PawnEventHandler,
script: *mut IPawnScript,
) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| inner_amx_load(script)));
}
#[cfg(all(not(feature = "samp-only"), not(target_env = "msvc")))]
unsafe extern "C" fn pawn_on_amx_unload(_this: *mut PawnEventHandler, script: *mut IPawnScript) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| inner_amx_unload(script)));
}
#[cfg(all(not(feature = "samp-only"), target_env = "msvc"))]
unsafe extern "thiscall" fn pawn_on_amx_unload(
_this: *mut PawnEventHandler,
script: *mut IPawnScript,
) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| inner_amx_unload(script)));
}
#[must_use]
pub fn supports() -> u32 {
let rt = Runtime::get();
let supports = rt.supports();
supports.bits()
}
pub fn load(server_exports: *const usize) {
let rt = Runtime::get();
let plugin = Runtime::plugin();
rt.set_server_exports(server_exports);
plugin.on_load();
}
pub fn unload() {
let plugin = Runtime::plugin();
plugin.on_unload();
}
pub fn register_events(events: Vec<crate::events::EventInfo>) {
if events.is_empty() {
return;
}
Runtime::get().register_events(events);
}
pub fn amx_load(amx: *mut AMX, natives: &[AMX_NATIVE_INFO]) {
let rt = Runtime::get();
let plugin = Runtime::plugin();
let amx = rt.insert_amx(amx);
let _ = amx.register(natives);
crate::events::on_amx_load(rt, amx);
plugin.on_amx_load(amx);
}
pub fn amx_unload(amx: *mut AMX) {
let rt = Runtime::get();
let plugin = Runtime::plugin();
crate::events::on_amx_unload(rt, amx);
if let Some(amx) = rt.remove_amx(amx) {
plugin.on_amx_unload(&amx);
}
}
#[inline]
pub fn tick(source: crate::plugin::TickSource) {
let rt = Runtime::get();
let elapsed = rt.record_tick();
let ctx = crate::plugin::TickContext { elapsed, source };
Runtime::plugin().on_tick(ctx);
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_initialize<F, T>(constructor: F)
where
F: FnOnce() -> T + 'static,
T: crate::plugin::SampPlugin + 'static,
{
crate::plugin::initialize(constructor);
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_store_natives(natives: Vec<AMX_NATIVE_INFO>) {
Runtime::get().set_omp_natives(natives);
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_load(core: *mut ICore) {
if core.is_null() {
sdk_warn!("null ICore* in on_load — samp::plugin::omp_core() will return None");
}
Runtime::get().set_omp_core(core);
Runtime::plugin().on_load();
}
#[cfg(not(feature = "samp-only"))]
pub unsafe fn omp_on_init(components: *mut ServerComponentList) {
let rt = Runtime::get();
rt.set_omp_component_list(components);
let pawn = unsafe { query_component(components, PAWN_COMPONENT_UID) };
if pawn.is_null() {
sdk_warn!("IPawnComponent not found in on_init — Pawn natives unavailable");
return;
}
let exports = unsafe { get_amx_functions(pawn) };
if exports != 0 {
rt.set_omp_amx_exports(exports);
}
let dispatcher = unsafe { get_pawn_event_dispatcher(pawn) };
if dispatcher.is_null() {
sdk_warn!(
"null IEventDispatcher<PawnEventHandler> in on_init — on_amx_load/on_amx_unload will not be called"
);
} else {
let handler = Box::into_raw(Box::new(PawnEventHandler::new(
&raw const PAWN_HANDLER_VTABLE,
)));
rt.set_pawn_event_handler(handler);
unsafe { add_pawn_event_handler(dispatcher, handler) };
}
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_on_ready() {
let rt = Runtime::get();
if !rt.omp_has_amx_exports() {
if let Some(pawn) = rt.omp_query_component(PAWN_COMPONENT_UID) {
let exports = unsafe { get_amx_functions(pawn) };
if exports != 0 {
rt.set_omp_amx_exports(exports);
} else {
sdk_warn!("getAmxFunctions() returned 0 in on_ready — Pawn natives unavailable");
}
} else {
sdk_warn!("on_ready: IPawnComponent not found");
}
}
if rt.omp_has_amx_exports() {
let pending = rt.take_pending_amx();
if !pending.is_empty() {
let natives = rt.omp_natives().to_vec();
for amx in pending {
amx_load(amx, &natives);
}
}
}
if let Some(interval) = rt.omp_tick_interval()
&& let Some(components) = rt.omp_component_list()
{
let timers = unsafe { query_timers_component(components) };
if timers.is_null() {
sdk_warn!(
"ITimersComponent not found — on_tick will not be called on Open Multiplayer"
);
} else {
let handler = Box::into_raw(Box::new(TimerTimeOutHandler {
vtable: &raw const TICK_HANDLER_VTABLE,
}));
let interval_ms = i64::try_from(interval.as_millis()).unwrap_or(i64::MAX);
let timer = unsafe { create_repeating_timer(timers, handler, interval_ms) };
if timer.is_null() {
sdk_warn!(
"failed to create timer on ITimersComponent — on_tick will not be called on Open Multiplayer"
);
let _ = unsafe { Box::from_raw(handler) };
} else {
rt.set_omp_tick(timer, handler);
}
}
}
Runtime::plugin().on_omp_ready();
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_on_free() {
Runtime::plugin().on_component_free();
}
#[cfg(not(feature = "samp-only"))]
pub fn omp_cleanup() {
let rt = Runtime::get();
if let Some(timer) = rt.take_omp_tick_timer() {
unsafe { kill_timer(timer) };
let _ = rt.take_omp_tick_handler(); }
if let Some(handler) = rt.take_pawn_event_handler() {
if let Some(pawn) = rt.omp_query_component(samp_sdk::omp::server::PAWN_COMPONENT_UID) {
let dispatcher = unsafe { get_pawn_event_dispatcher(pawn) };
if !dispatcher.is_null() {
unsafe { remove_pawn_event_handler(dispatcher, handler) };
}
}
drop(unsafe { Box::from_raw(handler) });
}
}