horizon_plugin_api/
lib.rs

1use async_trait::async_trait;
2use log::info;
3use serde::{Deserialize, Serialize};
4use std::any::Any;
5use std::collections::HashMap;
6use std::fmt::{self, Display};
7use std::sync::Arc;
8use uuid::Uuid;
9
10/// Unique identifier for players
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct PlayerId(pub Uuid);
13
14impl PlayerId {
15    pub fn new() -> Self {
16        Self(Uuid::new_v4())
17    }
18}
19
20impl Default for PlayerId {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl fmt::Display for PlayerId {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "{}", self.0)
29    }
30}
31
32/// Unique identifier for regions
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct RegionId(pub Uuid);
35
36impl RegionId {
37    pub fn new() -> Self {
38        Self(Uuid::new_v4())
39    }
40}
41
42impl Default for RegionId {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48/// Namespace for events to prevent conflicts between plugins
49#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub struct EventNamespace(pub String);
51
52impl EventNamespace {
53    pub fn new(name: impl Into<String> + std::marker::Copy) -> Self {
54        Self(name.into())
55    }
56    
57    pub fn plugin_default(plugin_name: &str) -> Self {
58        Self(format!("plugin.{}", plugin_name))
59    }
60}
61
62impl fmt::Display for EventNamespace {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}", self.0)
65    }
66}
67/// Event identifier combining namespace and event type
68#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
69pub struct EventId {
70    pub namespace: EventNamespace,
71    pub event_type: String,
72}
73
74impl EventId {
75    pub fn new(namespace: EventNamespace, event_type: impl Into<String> + Clone) -> Self {
76        println!("Creating EventId with namespace: {}, event_type: {}", namespace, event_type.clone().into());
77        Self {
78            namespace,
79            event_type: event_type.into(),
80        }
81    }
82}
83
84impl fmt::Display for EventId {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        write!(f, "{}::{}", self.namespace, self.event_type)
87    }
88}
89
90/// Position in 3D space
91#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
92pub struct Position {
93    pub x: f64,
94    pub y: f64,
95    pub z: f64,
96}
97
98impl Position {
99    pub fn new(x: f64, y: f64, z: f64) -> Self {
100        Self { x, y, z }
101    }
102    
103    pub fn distance_to(&self, other: &Position) -> f64 {
104        let dx = self.x - other.x;
105        let dy = self.y - other.y;
106        let dz = self.z - other.z;
107        (dx * dx + dy * dy + dz * dz).sqrt()
108    }
109}
110
111/// Player information
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct Player {
114    pub id: PlayerId,
115    pub name: String,
116    pub position: Position,
117    pub metadata: HashMap<String, String>,
118}
119
120impl Player {
121    pub fn new(name: impl Into<String>, position: Position) -> Self {
122        Self {
123            id: PlayerId::new(),
124            name: name.into(),
125            position,
126            metadata: HashMap::new(),
127        }
128    }
129}
130
131/// Core game events that all plugins can listen to
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(tag = "type", content = "data")]
134pub enum CoreEvent {
135    PlayerJoined { player: Player },
136    PlayerLeft { player_id: PlayerId },
137    PlayerMoved { player_id: PlayerId, old_position: Position, new_position: Position },
138    RegionChanged { region_id: RegionId },
139    CustomMessage { data: serde_json::Value },
140}
141
142/// Trait for serializable events
143pub trait GameEvent: fmt::Debug + Send + Sync + 'static {
144    fn event_type(&self) -> &'static str;
145    fn serialize(&self) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
146    fn as_any(&self) -> &dyn Any;
147}
148
149impl GameEvent for CoreEvent {
150    fn event_type(&self) -> &'static str {
151        match self {
152            CoreEvent::PlayerJoined { .. } => "player_joined",
153            CoreEvent::PlayerLeft { .. } => "player_left", 
154            CoreEvent::PlayerMoved { .. } => "player_moved",
155            CoreEvent::RegionChanged { .. } => "region_changed",
156            CoreEvent::CustomMessage { .. } => "custom_message",
157        }
158    }
159    
160    fn serialize(&self) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
161        Ok(serde_json::to_vec(self)?)
162    }
163    
164    fn as_any(&self) -> &dyn Any {
165        self
166    }
167}
168
169impl From<Box<dyn GameEvent>> for Arc<dyn GameEvent + Send + Sync> {
170    fn from(b: Box<dyn GameEvent>) -> Self {
171        // Explicitly construct an Arc with the correct trait object type
172        let boxed: Box<dyn GameEvent + Send + Sync> = b as Box<dyn GameEvent + Send + Sync>;
173        Arc::from(boxed)
174    }
175}
176
177/// Server context provided to plugins
178#[async_trait]
179pub trait ServerContext: Send + Sync {
180    /// Emit an event to the event system
181    async fn emit_event(&self, namespace: EventNamespace, event: Box<dyn GameEvent>) -> Result<(), ServerError>;
182    
183    /// Get current region ID
184    fn region_id(&self) -> RegionId;
185    
186    /// Get all players in the region
187    async fn get_players(&self) -> Result<Vec<Player>, ServerError>;
188    
189    /// Get specific player by ID
190    async fn get_player(&self, id: PlayerId) -> Result<Option<Player>, ServerError>;
191    
192    /// Send message to specific player
193    async fn send_to_player(&self, player_id: PlayerId, message: &[u8]) -> Result<(), ServerError>;
194    
195    /// Broadcast message to all players in region
196    async fn broadcast_to_region(&self, message: &[u8]) -> Result<(), ServerError>;
197    
198    /// Log message (for debugging/monitoring)
199    fn log(&self, level: LogLevel, message: &str);
200}
201
202/// Plugin trait that all plugins must implement
203#[async_trait]
204pub trait Plugin: Send + Sync {
205    /// Plugin name (used for default namespace)
206    fn name(&self) -> &'static str;
207    
208    /// Plugin version
209    fn version(&self) -> &'static str;
210
211    /// Pre-initialize the plugin (This is where you register ALL event handlers)
212    async fn pre_initialize(&mut self, context: &dyn ServerContext) -> Result<(), PluginError>;
213
214    /// Initialize the plugin (This is where you load resources, send events to other plugins, etc.)
215    async fn initialize(&mut self, context: &(dyn ServerContext + 'static)) -> Result<(), PluginError> {
216        info!("Initializing plugin: {} v{}", self.name(), self.version());
217        Ok(())
218    }
219    
220    /// Handle an event
221    async fn handle_event(&mut self, event_id: &EventId, event: &dyn GameEvent, context: &dyn ServerContext) -> Result<(), PluginError>;
222    
223    /// Get event IDs this plugin wants to listen to
224    fn subscribed_events(&self) -> Vec<EventId>;
225    
226    /// Shutdown the plugin
227    async fn shutdown(&mut self, context: &dyn ServerContext) -> Result<(), PluginError>;
228}
229
230/// Opaque type for FFI-safe plugin pointers
231#[repr(C)]
232pub struct PluginOpaque;
233
234/// Function signature for plugin creation (FFI-safe)
235pub type PluginCreateFn = unsafe extern "C" fn() -> *mut PluginOpaque;
236
237/// Errors that can occur in the server
238#[derive(thiserror::Error, Debug)]
239pub enum ServerError {
240    #[error("Network error: {0}")]
241    Network(String),
242    #[error("Serialization error: {0}")]
243    Serialization(String),
244    #[error("Player not found: {0}")]
245    PlayerNotFound(PlayerId),
246    #[error("Region error: {0}")]
247    Region(String),
248    #[error("Plugin error: {0}")]
249    Plugin(#[from] PluginError),
250    #[error("Internal error: {0}")]
251    Internal(String),
252}
253
254/// Plugin-specific errors
255#[derive(thiserror::Error, Debug)]
256pub enum PluginError {
257    #[error("Plugin initialization failed: {0}")]
258    InitializationFailed(String),
259    #[error("Plugin execution error: {0}")]
260    ExecutionError(String),
261    #[error("Plugin configuration error: {0}")]
262    ConfigurationError(String),
263    #[error("Plugin dependency error: {0}")]
264    DependencyError(String),
265}
266
267/// Log levels for plugin logging
268#[derive(Debug, Clone, Copy)]
269pub enum LogLevel {
270    Error,
271    Warn,
272    Info,
273    Debug,
274    Trace,
275}
276
277/// Network message types
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(tag = "type", content = "payload")]
280pub enum NetworkMessage {
281    PlayerJoin { name: String },
282    PlayerMove { position: Position },
283    PlayerLeave,
284    GameData { data: serde_json::Value },
285    PluginMessage { plugin: String, data: serde_json::Value },
286}
287
288/// Connection information for a client
289#[derive(Debug, Clone)]
290pub struct ConnectionInfo {
291    pub player_id: PlayerId,
292    pub remote_addr: std::net::SocketAddr,
293    pub connected_at: std::time::SystemTime,
294}
295
296/// Region bounds for the server
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct RegionBounds {
299    pub min_x: f64,
300    pub max_x: f64,
301    pub min_y: f64,
302    pub max_y: f64,
303    pub min_z: f64,
304    pub max_z: f64,
305}
306
307impl RegionBounds {
308    pub fn contains(&self, position: &Position) -> bool {
309        position.x >= self.min_x && position.x <= self.max_x &&
310        position.y >= self.min_y && position.y <= self.max_y &&
311        position.z >= self.min_z && position.z <= self.max_z
312    }
313}