🎯 A comprehensive template for creating Horizon game server plugins
This repository serves as the official template for Horizon plugin development. It demonstrates best practices, common patterns, and provides a solid foundation for building your own plugins.
Note: This template is automatically used by the Far Beyond CLI (
fbcli
) when creating new plugins. You typically don't need to clone this directly - usefbcli horizon plugin new <name>
instead.
# Install fbcli first
cargo install fbcli
# Create a new plugin from this template
fbcli horizon plugin new my_awesome_plugin
cd my_awesome_plugin
# Build and deploy your plugin
fbcli horizon plugin build
git clone https://github.com/Far-Beyond-Dev/Horizon-Plugin-Sample.git my_plugin
cd my_plugin
rm -rf .git
# Update Cargo.toml with your plugin name
# Edit src/lib.rs with your plugin logic
cargo build --release
- Event Handling: Examples of handling core server events, client events, and plugin events
- State Management: Thread-safe player data tracking using
Arc<Mutex<>>
- Inter-Plugin Communication: Sending and receiving events between plugins
- Custom Events: Defining your own event types
- Configuration: Plugin configuration patterns
- Error Handling: Proper error handling and logging
- Testing: Unit tests for plugin functionality
- Core Events:
player_connected
,player_disconnected
- Client Events:
chat/message
,movement/position_update
,movement/jump
- Plugin Events: Communication with other plugins like
inventory
,logger
- ✅ Proper use of the
create_simple_plugin!
macro - ✅ Thread-safe state management
- ✅ Comprehensive logging with different levels
- ✅ Event registration patterns
- ✅ Clean shutdown procedures
- ✅ Plugin lifecycle management
- ✅ Error handling and recovery
pub struct SamplePlugin {
name: String,
player_data: Arc<Mutex<HashMap<PlayerId, PlayerData>>>,
config: PluginConfig,
}
impl SimplePlugin for SamplePlugin {
// Required methods:
fn name(&self) -> &str { /* ... */ }
fn version(&self) -> &str { /* ... */ }
async fn register_handlers(&mut self, events: Arc<EventSystem>) -> Result<(), PluginError> { /* ... */ }
async fn on_init(&mut self, context: Arc<dyn ServerContext>) -> Result<(), PluginError> { /* ... */ }
async fn on_shutdown(&mut self, context: Arc<dyn ServerContext>) -> Result<(), PluginError> { /* ... */ }
}
Use the register_handlers!
macro to handle different types of events:
register_handlers!(events; core {
"player_connected" => |event: serde_json::Value| {
info!("Player joined: {:?}", event);
Ok(())
}
})?;
register_handlers!(events; client {
"chat", "message" => |event: PlayerChatEvent| {
info!("Chat: {}", event.message);
Ok(())
}
})?;
register_handlers!(events; plugin {
"other_plugin", "some_event" => |event: CustomEvent| {
info!("Received: {:?}", event);
Ok(())
}
})?;
// Thread-safe state
let player_data: Arc<Mutex<HashMap<PlayerId, PlayerData>>> =
Arc::new(Mutex::new(HashMap::new()));
// Access in event handlers
let mut data = player_data.lock().unwrap();
data.insert(player_id, PlayerData::new());
// Send events to other plugins
events.emit_plugin(
"target_plugin",
"event_name",
&serde_json::json!({
"data": "value",
"timestamp": current_timestamp()
})
).await?;
- Update Plugin Name: Modify the struct name and
name()
method - Define Your Events: Create custom event types with
#[derive(Serialize, Deserialize)]
- Implement Event Handlers: Add your business logic in the
register_handlers
method - Add State: Define what data your plugin needs to track
- Configure Initialization: Set up your plugin in
on_init
Add any additional crates you need in Cargo.toml
:
[dependencies]
# Core dependencies (already included)
event_system = { path = "../event_system" }
serde = { version = "1.0", features = ["derive"] }
# ... other core deps
# Your additional dependencies
regex = "1.0"
reqwest = "0.11"
sqlx = "0.7"
Run the included tests:
cargo test
Add your own tests following the examples in the tests
module.
# Development build
cargo build
# Release build (for production)
cargo build --release
# Using fbcli (handles everything automatically)
fbcli horizon plugin build
The examples/
directory contains additional code samples:
basic_events.rs
: Minimal event handling examplescustom_events.rs
: Creating and using custom eventsinter_plugin.rs
: Advanced inter-plugin communication patterns
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerData {
pub join_time: u64,
pub last_position: Option<Position>,
pub stats: PlayerStats,
}
// In your event handlers
fn handle_player_join(&self, player_id: PlayerId) {
let mut data = self.player_data.lock().unwrap();
data.insert(player_id, PlayerData::new());
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
pub enable_feature: bool,
pub max_value: i32,
pub message_template: String,
}
impl Default for PluginConfig {
fn default() -> Self {
Self {
enable_feature: true,
max_value: 100,
message_template: "Hello, {}!".to_string(),
}
}
}
async fn risky_operation(&self) -> Result<(), PluginError> {
some_operation()
.map_err(|e| PluginError::ExecutionError(format!("Operation failed: {}", e)))?;
Ok(())
}
- Always use
Arc<Mutex<>>
for shared state - Be careful with lock ordering to avoid deadlocks
- Keep critical sections short
- Use
debug!
for verbose logging that can be filtered out - Avoid blocking operations in event handlers
- Consider using channels for heavy processing
- Events are processed asynchronously
- Event handlers should return quickly
- Use
emit_plugin
for inter-plugin communication - Custom events must implement
Serialize + Deserialize
This template is licensed under the MIT License - see the LICENSE file for details.
Found an issue or want to improve this template?
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
Happy plugin development! 🎉