Complete Examples
Example: Registering All Callback Types
Here's a complete example showing how to register all callback types in your mod's initialization:
// In your mod's MissionServer.c file
modded class MissionServer
{
override void OnInit()
{
super.OnInit();
// Register callbacks for zone events (for testing/debugging or production use)
MyZoneEventCallback zoneCallback = new MyZoneEventCallback();
MyPlayerStateChangeCallback stateCallback = new MyPlayerStateChangeCallback();
MyExitTimerCallback timerCallback = new MyExitTimerCallback();
// Register server-side callbacks
PlayerZoneController.RegisterZoneEventCallback(zoneCallback);
PlayerZoneController.RegisterPlayerStateChangeCallback(stateCallback);
PlayerZoneController.RegisterExitTimerCallback(timerCallback);
Print("[MyMod] All server-side callbacks registered successfully (zone, state, timer).");
}
}
// For client-side map drawing in MissionGameplay.c
modded class MissionGameplay
{
override void OnInit()
{
super.OnInit();
// Register drawing callback on client side
MyZoneDrawingCallback drawingCallback = new MyZoneDrawingCallback();
MapDrawer.RegisterZoneDrawingCallback(drawingCallback);
Print("[MyMod] Drawing callback registered successfully.");
}
}
Example 1: Sound Mod Integration
This example shows how to play a sound when a player enters/exits a specific zone. This is a complete, working implementation based on the NinjinsSoundMod test mod.
CfgSoundShaders and CfgSoundSets (not CfgSounds) for SEffectManager.PlaySound() to work correctly. The sound name must end with _SoundSet.
Step 1: Configure Sounds in config.cpp
File: MySoundMod/config.cpp
class CfgSoundShaders
{
class MySoundMod_SoundShader_ZoneEnter
{
samples[] = { {"MySoundMod\sounds\zone_enter.ogg", 1} };
range = 30; // Sound radius in meters
volume = 1.0; // Volume level (0.0 to 1.0)
rangeCurve[] = {
{0, 1}, // At 0m: 100% volume
{15, 0.5}, // At 15m: 50% volume
{30, 0.1} // At 30m: 10% volume
};
};
class MySoundMod_SoundShader_ZoneExit
{
samples[] = { {"MySoundMod\sounds\zone_exit.ogg", 1} };
range = 30;
volume = 1.0;
rangeCurve[] = {
{0, 1},
{15, 0.5},
{30, 0.1}
};
};
};
class CfgSoundSets
{
class MySoundMod_SoundZoneEnter_SoundSet
{
soundShaders[] = {"MySoundMod_SoundShader_ZoneEnter"};
};
class MySoundMod_SoundZoneExit_SoundSet
{
soundShaders[] = {"MySoundMod_SoundShader_ZoneExit"};
};
}
Note:
- Place your
.oggsound files inMySoundMod\sounds\directory - The path in
samples[]should use backslashes:"MySoundMod\sounds\zone_enter.ogg" - SoundSet names must end with
_SoundSetsuffix
Step 2: Create Zone Callback (Server-side)
File: MySoundMod/scripts/5_Mission/ZoneSoundCallback.c
class ZoneSoundCallback : ZoneEventCallback
{
override void OnZoneEnter(PlayerBase player, ZoneBase zone)
{
if (!player || !zone)
return;
string zoneName = zone.GetName();
// Only play sound for specific zone name
if (zoneName != "SoundZone")
return;
string playerName = "Unknown";
if (player.GetIdentity())
playerName = player.GetIdentity().GetName();
Print("[ZoneSoundCallback] Player " + playerName + " entered zone: " + zoneName);
// Send RPC to client to play enter sound
PlayerIdentity playerIdentity = player.GetIdentity();
if (playerIdentity)
{
GetRPCManager().SendRPC("MySoundMod", "PlayZoneEnterSound", new Param1(zoneName), true, playerIdentity);
}
}
override void OnZoneExit(PlayerBase player, ZoneBase zone)
{
if (!player || !zone)
return;
string zoneName = zone.GetName();
// Only play sound for specific zone name
if (zoneName != "SoundZone")
return;
string playerName = "Unknown";
if (player.GetIdentity())
playerName = player.GetIdentity().GetName();
Print("[ZoneSoundCallback] Player " + playerName + " exited zone: " + zoneName);
// Send RPC to client to play exit sound
PlayerIdentity playerIdentity = player.GetIdentity();
if (playerIdentity)
{
GetRPCManager().SendRPC("MySoundMod", "PlayZoneExitSound", new Param1(zoneName), true, playerIdentity);
}
}
}
Step 3: Register Callback in MissionServer.c (Server-side)
File: MySoundMod/scripts/5_Mission/MissionServer.c
modded class MissionServer extends MissionBase
{
private ref ZoneSoundCallback m_ZoneSoundCallback;
override void OnInit()
{
super.OnInit();
// Register zone event callback for sound playback
m_ZoneSoundCallback = new ZoneSoundCallback();
PlayerZoneController.RegisterZoneEventCallback(m_ZoneSoundCallback);
Print("[MySoundMod] Zone sound callback registered successfully.");
}
}
Step 4: Handle RPCs and Play Sounds (Client-side)
File: MySoundMod/scripts/5_Mission/missionGameplay.c
modded class MissionGameplay extends MissionBase
{
override void OnInit()
{
super.OnInit();
// Register RPCs for sound playback
GetRPCManager().AddRPC("MySoundMod", "PlayZoneEnterSound", this, SingleplayerExecutionType.Client);
GetRPCManager().AddRPC("MySoundMod", "PlayZoneExitSound", this, SingleplayerExecutionType.Client);
Print("[MySoundMod] RPC handlers registered successfully.");
}
// RPC handler for zone enter sound
void PlayZoneEnterSound(CallType type, ParamsReadContext ctx, PlayerIdentity sender, Object target)
{
Param1 data;
if (!ctx.Read(data))
return;
string zoneName = data.param1;
// Get the local player (use safe casting)
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
if (!player)
return;
// Play sound at player's position
// IMPORTANT: Use the SoundSet name (ends with _SoundSet), not the file path
string soundPath = "MySoundMod_SoundZoneEnter_SoundSet";
vector playerPos = player.GetPosition();
// Play 3D sound at player position
SEffectManager.PlaySound(soundPath, playerPos);
Print("[MySoundMod] Playing enter sound for zone: " + zoneName);
}
// RPC handler for zone exit sound
void PlayZoneExitSound(CallType type, ParamsReadContext ctx, PlayerIdentity sender, Object target)
{
Param1 data;
if (!ctx.Read(data))
return;
string zoneName = data.param1;
// Get the local player (use safe casting)
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
if (!player)
return;
// Play sound at player's position
string soundPath = "MySoundMod_SoundZoneExit_SoundSet";
vector playerPos = player.GetPosition();
// Play 3D sound at player position
SEffectManager.PlaySound(soundPath, playerPos);
Print("[MySoundMod] Playing exit sound for zone: " + zoneName);
}
}
Complete File Structure
MySoundMod/
├── config.cpp (Contains CfgSoundShaders and CfgSoundSets)
├── sounds/
│ ├── zone_enter.ogg (Your sound file)
│ └── zone_exit.ogg (Your sound file)
└── scripts/
└── 5_Mission/
├── MissionServer.c (Server-side: Register callback)
├── missionGameplay.c (Client-side: RPC handlers)
└── ZoneSoundCallback.c (Server-side: Callback implementation)
Key Points
- Sound Configuration: Use
CfgSoundShadersandCfgSoundSets, notCfgSounds - SoundSet Naming: SoundSet names must end with
_SoundSetsuffix - Sound Path: Use the SoundSet name (e.g.,
"MySoundMod_SoundZoneEnter_SoundSet"), not the file path - Safe Casting: Always use
PlayerBase.Cast()when getting the player - Ref Keyword: Use
reffor callback member variables in MissionServer - Zone Filtering: Check zone name in the callback if you only want sounds for specific zones
This implementation has been tested and works correctly with NinjinsPvPPvE.
Example 2: Custom Map Drawing
// CustomMapOverlay.c
class CustomMapOverlayCallback : ZoneDrawingCallback
{
override void OnAfterZonesDrawn(MapDrawer mapDrawer, array zones)
{
if (!mapDrawer || !zones)
return;
// Draw a custom event zone
vector eventCenter = "6000 0 6000";
float eventRadius = 500.0;
int eventColor = ARGB(200, 255, 165, 0); // Orange, semi-transparent
mapDrawer.DrawCircle(eventCenter, eventRadius, eventColor, true, false, 10);
mapDrawer.DrawLabel(eventCenter, "Event Zone", ARGB(255, 255, 255, 255));
// Draw a custom polygon
array polygonVertices = {
"5500 0 5500",
"5500 0 6500",
"6500 0 6500",
"6500 0 5500"
};
int polyColor = ARGB(180, 0, 255, 255); // Cyan, more transparent
mapDrawer.DrawPolygon(polygonVertices, polyColor, false, false, 8);
}
}
// In MissionGameplay.c or map menu Init
void CustomMapOverlayInit()
{
CustomMapOverlayCallback callback = new CustomMapOverlayCallback();
MapDrawer.RegisterZoneDrawingCallback(callback);
}
Example 3: UI Mod with State Changes
// UIMod.c
class UIModStateCallback : PlayerStateChangeCallback
{
override void OnPlayerEnteredPvP(PlayerBase player)
{
if (!player || player != GetGame().GetPlayer())
return;
// Update UI to show PvP mode
// Your UI update code here
Print("[UIMod] PvP mode activated!");
}
override void OnPlayerExitedPvP(PlayerBase player)
{
if (!player || player != GetGame().GetPlayer())
return;
// Update UI to show normal mode
Print("[UIMod] PvP mode deactivated!");
}
// Implement other methods...
}
// In MissionServer.c
void UIModInit()
{
UIModStateCallback callback = new UIModStateCallback();
PlayerZoneController.RegisterPlayerStateChangeCallback(callback);
}
Example 4: No Vehicle Zone
Teleport Players out of zone to Green Mountain while in disallowed vehicleType.
This example shows how to detect when players enter a specific zone and teleport them (and their vehicle) to a safe location if they're in a disallowed vehicle type. Players on foot or in allowed vehicles are not teleported.
File: NinjinsCallBackTestMod/scripts/5_Mission/NinjinsCallBackTest.c
// Handles zone enter/exit events and teleports the player to Green Mountain
class NinjinsCallBackTest : ZoneEventCallback
{
override void OnZoneEnter(PlayerBase player, ZoneBase zone)
{
if (!player || !zone)
return;
string zoneName = zone.GetName();
// Only teleport player for specific zone name
if (zoneName != "SoundZone")
return;
string playerName = "Unknown";
if (player.GetIdentity())
playerName = player.GetIdentity().GetName();
Print("[NinjinsCallBackTest] Player " + playerName + " entered zone: " + zoneName);
// Teleport player to Green Mountain
vector greenMountainPos = "3710.86 402 5998.8";
// List of disallowed vehicles, players in these vehicles WILL be teleported out
array disallowedVehicles = {
"Boat_01_Orange",
"OffroadHatchback",
"CivilianSedan",
"Truck_01_Covered"
};
// Handle vehicle teleportation, teleport OUT if player is IN a disallowed vehicle
HumanCommandVehicle hcv;
Transport transport;
bool isDisallowed = false;
string vehicleType = "";
if (player.IsInTransport())
{
hcv = player.GetCommand_Vehicle();
if (hcv)
{
transport = hcv.GetTransport();
if (transport)
{
vehicleType = transport.GetType();
// Check if vehicle is in the disallowed list
for (int i = 0; i < disallowedVehicles.Count(); i++)
{
if (vehicleType == disallowedVehicles[i])
{
isDisallowed = true;
break;
}
}
if (isDisallowed)
{
// Player is in a disallowed vehicle, teleport them out (teleport the vehicle with player)
Print("[NinjinsCallBackTest] Player " + playerName + " is in disallowed vehicle (" + vehicleType + ") - TELEPORTING OUT to Green Mountain");
transport.SetPosition(greenMountainPos);
}
else
{
// Player is in a vehicle but not a disallowed one, don't teleport
Print("[NinjinsCallBackTest] Player " + playerName + " is in vehicle (" + vehicleType + ") but it's not disallowed - NOT teleporting");
}
}
}
}
else
{
// Player is not in a vehicle, don't teleport
Print("[NinjinsCallBackTest] Player " + playerName + " is not in a vehicle - NOT teleporting");
}
}
}
File: NinjinsCallBackTestMod/scripts/5_Mission/MissionServer.c
modded class MissionServer extends MissionBase
{
private ref NinjinsCallBackTest m_NinjinsCallBackTest;
void ~MissionServer() {
}
void MissionServer()
{
Print("NinjinsCallBackTestMod mod has started !");
}
override void OnInit()
{
super.OnInit();
// Register zone event callback for teleportation
m_NinjinsCallBackTest = new NinjinsCallBackTest();
PlayerZoneController.RegisterZoneEventCallback(m_NinjinsCallBackTest);
Print("[NinjinsCallBackTestMod] Zone callback registered successfully.");
}
};
Key Points:
- Vehicle Detection: Uses
player.IsInTransport()to check if player is in a vehicle - Vehicle Type Checking: Compares vehicle type against a disallowed list
- Teleportation: Teleports the entire vehicle (with player inside) using
transport.SetPosition() - Zone Filtering: Only processes events for the specific zone name "SoundZone"
- Safe Teleportation: Players on foot or in allowed vehicles are not affected
Example 5: Allow Damage in PvE Zones
This example shows how to override PvE zone protection to allow damage even when players are in PvE zones. This can be useful for special events, admin tools, or custom gameplay mechanics.
File: NinjinsCallBackTestMod/scripts/5_Mission/NinjinsCallBackTest.c
// Example damage callback - allows damage even in PvE zones
class NinjinsCallBackTest : DamageCallback
{
override bool OnShouldAllowDamageOverride(PlayerBase victim, PlayerBase attacker, TotalDamageResult damageResult, int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos, float speedCoef, bool shouldAllowDamageResult)
{
// This callback runs AFTER ShouldAllowDamage makes its decision
// Return true to FORCE allow damage (override ShouldAllowDamage's decision)
// Return false (or shouldAllowDamageResult) to respect ShouldAllowDamage's decision
if (!victim)
return shouldAllowDamageResult;
// Debug: Log that override callback was called
Print("[NinjinsCallBackTest] OnShouldAllowDamageOverride called - shouldAllowDamageResult: " + shouldAllowDamageResult + ", victim in PvE: " + victim.netSync_IsInPvEZone);
// Check if damage was blocked and victim is in PvE zone
// If so, override to allow damage even in PvE
if (!shouldAllowDamageResult && victim.netSync_IsInPvEZone)
{
string victimName = "Unknown";
if (victim.GetIdentity())
victimName = victim.GetIdentity().GetName();
string attackerName = "Unknown";
if (attacker && attacker.GetIdentity())
attackerName = attacker.GetIdentity().GetName();
Print("[NinjinsCallBackTest] ===== OVERRIDING PvE PROTECTION =====");
Print("[NinjinsCallBackTest] Victim: " + victimName + " (in PvE zone)");
Print("[NinjinsCallBackTest] Attacker: " + attackerName);
Print("[NinjinsCallBackTest] ALLOWING damage despite PvE protection");
Print("[NinjinsCallBackTest] =====================================");
// Force allow damage even in PvE zone
return true;
}
// For all other cases, respect ShouldAllowDamage's decision
return shouldAllowDamageResult;
}
override bool OnShouldAllowItemDamageOverride(ItemBase item, PlayerBase attacker, TotalDamageResult damageResult, int damageType, EntityAI source, string dmgZone, string ammo, vector modelPos, float speedCoef, bool shouldAllowItemDamageResult)
{
// This callback runs AFTER ShouldAllowItemDamage makes its decision
// Return true to FORCE allow damage (override ShouldAllowItemDamage's decision)
// Return false (or shouldAllowItemDamageResult) to respect ShouldAllowItemDamage's decision
if (!item)
return shouldAllowItemDamageResult;
string itemType = item.GetType();
string attackerName = "Unknown";
if (attacker && attacker.GetIdentity())
attackerName = attacker.GetIdentity().GetName();
PlayerBase owner = item.GetOwnerPlayer();
string ownerName = "None";
bool ownerInPvE = false;
bool ownerInPvP = false;
bool ownerInSafeZone = false;
if (owner)
{
if (owner.GetIdentity())
ownerName = owner.GetIdentity().GetName();
ownerInPvE = owner.netSync_IsInPvEZone;
ownerInPvP = owner.netSync_IsInPvPZone;
ownerInSafeZone = owner.netSync_IsInSafeZone;
}
// Get damage amount
float damage = 0.0;
if (damageResult)
{
damage = damageResult.GetDamage("GlobalHealth", "Health");
}
// Get source type
string sourceType = "Unknown";
if (source)
sourceType = source.GetType();
// Debug: Log that override callback was called
Print("[NinjinsCallBackTest] ===== ITEM DAMAGE OVERRIDE =====");
Print("[NinjinsCallBackTest] Item: " + itemType);
Print("[NinjinsCallBackTest] Attacker: " + attackerName);
Print("[NinjinsCallBackTest] Owner: " + ownerName);
Print("[NinjinsCallBackTest] Owner in PvE: " + ownerInPvE);
Print("[NinjinsCallBackTest] Owner in PvP: " + ownerInPvP);
Print("[NinjinsCallBackTest] Owner in SafeZone: " + ownerInSafeZone);
Print("[NinjinsCallBackTest] Damage: " + damage);
Print("[NinjinsCallBackTest] Source: " + sourceType);
Print("[NinjinsCallBackTest] Ammo: " + ammo);
Print("[NinjinsCallBackTest] Damage Zone: " + dmgZone);
Print("[NinjinsCallBackTest] ShouldAllowItemDamage Result: " + shouldAllowItemDamageResult);
Print("[NinjinsCallBackTest] ================================");
// Check if damage was blocked and owner is in PvE zone
// If so, override to allow damage even in PvE
if (!shouldAllowItemDamageResult && ownerInPvE)
{
Print("[NinjinsCallBackTest] ===== OVERRIDING ITEM PvE PROTECTION =====");
Print("[NinjinsCallBackTest] Item: " + itemType);
Print("[NinjinsCallBackTest] Owner: " + ownerName + " (in PvE zone)");
Print("[NinjinsCallBackTest] Attacker: " + attackerName);
Print("[NinjinsCallBackTest] ALLOWING item damage despite PvE protection");
Print("[NinjinsCallBackTest] ==========================================");
// Force allow item damage even in PvE zone
return true;
}
// For all other cases, respect ShouldAllowItemDamage's decision
return shouldAllowItemDamageResult;
}
}
File: NinjinsCallBackTestMod/scripts/5_Mission/MissionServer.c
modded class MissionServer extends MissionBase
{
private ref NinjinsCallBackTest m_NinjinsCallBackTest;
void ~MissionServer() {
}
void MissionServer()
{
Print("NinjinsCallBackTestMod mod has started !");
}
override void OnInit()
{
super.OnInit();
// Register damage callback for logging damage events
m_NinjinsCallBackTest = new NinjinsCallBackTest();
DamageUtils.RegisterDamageCallback(m_NinjinsCallBackTest);
Print("[NinjinsCallBackTestMod] Damage callback registered successfully.");
}
};
Key Points:
- Override Behavior: The callback runs after
ShouldAllowDamagemakes its decision. You can override the decision by returningtrueto force allow damage, or return the originalshouldAllowDamageResultto respect it. - Player Damage:
OnShouldAllowDamageOverridehandles damage to players. Checkvictim.netSync_IsInPvEZoneto determine if the victim is in a PvE zone. - Item Damage:
OnShouldAllowItemDamageOverridehandles damage to items. Get the item owner withitem.GetOwnerPlayer()and check their zone state. - Zone State Checks: Use
player.netSync_IsInPvEZone,player.netSync_IsInPvPZone, andplayer.netSync_IsInSafeZoneto check zone states. - Registration: Register the callback using
DamageUtils.RegisterDamageCallback()inMissionServer.OnInit(). - Use Cases: Useful for special events, admin tools, custom PvP mechanics, or testing scenarios where you need to bypass zone protection.