16 KiB
OldCombatMechanics API
The OldCombatMechanics API exposes both configured modesets and temporary per-player module overrides. Prefer modesets for arena and minigame combat modes, such as offering separate 1.8-style and modern PvP experiences. Modesets are named configured bundles, selected per player and world, and changes made through setModesetForPlayer use the normal player-data persistence.
Module overrides are different: they are temporary API-only per-player exceptions for individual modules, intended for narrow runtime cases where a plugin needs to force one module on or off for one currently online player. Overrides are online-session-only, in-memory state. They are cleared when the player quits, when OldCombatMechanics is disabled, or when a plugin explicitly clears them through the API. Module overrides are not a replacement for configured modesets.
| Use case | Modesets | Module overrides |
|---|---|---|
| Arena/minigame combat style, such as 1.8-style versus modern PvP | Preferred: configure named bundles and select them per player/world | Not intended for this |
| Persistence | Uses normal player-data persistence when changed through setModesetForPlayer |
Online-session-only, in-memory |
| Scope | Whole configured bundle of module choices | Individual module exception for one player |
| Configuration source | Server configuration and allowed world modesets | API-only runtime state |
Overrides are shared runtime state. If multiple plugins set an override for the same player and module, the last write wins. Any plugin that clears that override clears the same shared state for that player and module.
The API also exposes configured modesets, world-allowed modesets, the player's stored modeset for their current world, modeset changes, and Bukkit events for module override and modeset changes.
Dependency setup
There is currently no separate slim API artefact. Compile against the same OldCombatMechanics plugin jar that you deploy or test with, and mark it as compile-only/provided so your plugin does not shade or bundle OCM.
Gradle Kotlin DSL with a local jar:
dependencies {
compileOnly(files("libs/OldCombatMechanics.jar"))
compileOnly("org.spigotmc:spigot-api:1.21.11-R0.1-SNAPSHOT")
}
Maven with a local or system-provided jar:
<dependency>
<groupId>kernitus.plugin</groupId>
<artifactId>OldCombatMechanics</artifactId>
<version>your-ocm-version</version>
<scope>provided</scope>
</dependency>
If you use a local Maven repository or an internal repository manager, publish the OCM jar there and keep the dependency provided/compileOnly.
Do not use OCM internal packages. Public API classes live under:
import kernitus.plugin.OldCombatMechanics.api.OldCombatMechanicsAPI;
import kernitus.plugin.OldCombatMechanics.api.PlayerModesetChangeEvent;
import kernitus.plugin.OldCombatMechanics.api.PlayerModuleOverride;
import kernitus.plugin.OldCombatMechanics.api.PlayerModuleOverrideChangeEvent;
plugin.yml load order
Use depend when your plugin cannot enable or function without OCM:
depend: [OldCombatMechanics]
Use softdepend when OCM integration is optional and your plugin can run without it:
softdepend: [OldCombatMechanics]
With softdepend, always treat the API service as optional and disable only your OCM-specific integration if the service is absent.
Getting the API instance
Always null-check the Bukkit service registration before using the API. The service is only available while OldCombatMechanics is loaded and enabled.
RegisteredServiceProvider<OldCombatMechanicsAPI> registration = Bukkit.getServicesManager()
.getRegistration(OldCombatMechanicsAPI.class);
if (registration == null) {
return;
}
OldCombatMechanicsAPI api = registration.getProvider();
Kotlin callers should use the same service lookup and null-check the registration:
val api = Bukkit.getServicesManager()
.getRegistration(OldCombatMechanicsAPI::class.java)
?.provider
?: return
Threading
Call API methods from the Bukkit main server thread. The current contract does not guarantee async-safe access to Bukkit Player state, module state-change notifications, or future implementation details unless a future method explicitly documents different threading behaviour.
Module names
All methods that accept a moduleName parameter expect a value from the set returned by getModuleNames(). Only configurable modules are valid. Internal modules, such as listener/helper modules, cannot be overridden and are not returned by getModuleNames().
Passing an unknown or non-configurable module name throws IllegalArgumentException.
Effective module precedence
When OldCombatMechanics determines whether a configurable module is enabled for a player, it resolves state in this order:
- per-player
FORCE_ENABLEDorFORCE_DISABLEDoverride; - configured disabled modules;
- configured always-enabled modules;
- player modeset and world configuration.
PlayerModuleOverride.DEFAULT means no per-player override is set, so the configured rules above continue to decide the module state.
Methods
getModesetNames
Returns all configured modeset names in config iteration order.
Set<String> getModesetNames()
for (String modeset : api.getModesetNames()) {
player.sendMessage(modeset);
}
getAllowedModesets
Returns the modesets allowed in a world as an unmodifiable defensive copy.
Set<String> getAllowedModesets(World world)
Iteration order is meaningful: it follows the configured world list when present, the worlds.__default__ list for worlds without their own list, or the configured modeset order when no world list applies.
There is no public API for a default or fallback modeset. If you need to present a default choice, use your own policy, such as the player's stored modeset when present or the first entry from getAllowedModesets(world).
Set<String> allowed = api.getAllowedModesets(player.getWorld());
for (String modeset : allowed) {
player.sendMessage("Allowed here: " + modeset);
}
getModesetForPlayer
Returns the stored modeset for the player in their current world, or null when no player-specific modeset has been stored yet.
String getModesetForPlayer(Player player)
String modeset = api.getModesetForPlayer(player);
if (modeset == null) {
player.sendMessage("No modeset selected for this world.");
} else {
player.sendMessage("Current modeset: " + modeset);
}
setModesetForPlayer
Stores the modeset for the player in their current world and fires PlayerModesetChangeEvent with reason API when the stored value changes.
void setModesetForPlayer(Player player, String modesetName)
The input name is normalised before validation and storage. Unknown modesets, or modesets not allowed in the player's current world, throw IllegalArgumentException.
Modeset changes made through the API follow normal player data persistence semantics: OCM stores the selected modeset in player data and schedules the usual save. They are not session-only overrides.
if (api.getAllowedModesets(player.getWorld()).contains("arena")) {
api.setModesetForPlayer(player, "arena");
}
forceEnableModuleForPlayer
Forces a configurable module on for an online player until the override is cleared, the player quits, or OldCombatMechanics is disabled.
void forceEnableModuleForPlayer(Player player, String moduleName)
Throws: IllegalArgumentException if moduleName is unknown or non-configurable.
api.forceEnableModuleForPlayer(player, "attack-frequency");
Kotlin:
api.forceEnableModuleForPlayer(player, "attack-frequency")
forceDisableModuleForPlayer
Forces a configurable module off for an online player until the override is cleared, the player quits, or OldCombatMechanics is disabled.
void forceDisableModuleForPlayer(Player player, String moduleName)
Throws: IllegalArgumentException if moduleName is unknown or non-configurable.
api.forceDisableModuleForPlayer(player, "sword-blocking");
Kotlin:
api.forceDisableModuleForPlayer(player, "sword-blocking")
clearModuleOverrideForPlayer
Clears the override for a single configurable module, reverting the player to configured module behaviour. No-op if no override is set.
void clearModuleOverrideForPlayer(Player player, String moduleName)
Throws: IllegalArgumentException if moduleName is unknown or non-configurable.
api.clearModuleOverrideForPlayer(player, "attack-frequency");
Kotlin:
api.clearModuleOverrideForPlayer(player, "attack-frequency")
clearAllModuleOverridesForPlayer
Clears all module overrides for a player at once, reverting them to configured module behaviour for every module. No-op if no overrides are set.
void clearAllModuleOverridesForPlayer(Player player)
api.clearAllModuleOverridesForPlayer(player);
Kotlin:
api.clearAllModuleOverridesForPlayer(player)
getModuleOverrideForPlayer
Returns the current override state for a single configurable module for a player. Returns PlayerModuleOverride.DEFAULT if no override is set.
PlayerModuleOverride getModuleOverrideForPlayer(Player player, String moduleName)
Throws: IllegalArgumentException if moduleName is unknown or non-configurable.
PlayerModuleOverride override = api.getModuleOverrideForPlayer(player, "sword-blocking");
if (override == PlayerModuleOverride.FORCE_ENABLED) {
player.sendMessage("Forced on");
} else if (override == PlayerModuleOverride.FORCE_DISABLED) {
player.sendMessage("Forced off");
} else {
player.sendMessage("Following configured rules");
}
Kotlin:
when (api.getModuleOverrideForPlayer(player, "sword-blocking")) {
PlayerModuleOverride.FORCE_ENABLED -> player.sendMessage("Forced on")
PlayerModuleOverride.FORCE_DISABLED -> player.sendMessage("Forced off")
PlayerModuleOverride.DEFAULT -> player.sendMessage("Following configured rules")
}
getModuleOverridesForPlayer
Returns all active overrides for a player as a map of module name to PlayerModuleOverride. Only modules with a non-DEFAULT override are included.
Map<String, PlayerModuleOverride> getModuleOverridesForPlayer(Player player)
Map<String, PlayerModuleOverride> overrides = api.getModuleOverridesForPlayer(player);
overrides.forEach((module, override) ->
player.sendMessage(module + ": " + override)
);
Kotlin:
api.getModuleOverridesForPlayer(player).forEach { (module, override) ->
player.sendMessage("$module: $override")
}
setModuleOverridesForPlayer
Sets multiple module overrides for a player at once. Entries with PlayerModuleOverride.DEFAULT are treated as clears. All module names and override values are validated before any override state changes, so an invalid entry leaves existing overrides unchanged.
void setModuleOverridesForPlayer(Player player, Map<String, PlayerModuleOverride> overrides)
Throws: IllegalArgumentException if any module name is unknown or non-configurable, or if a Java caller supplies a null module name or override value.
Map<String, PlayerModuleOverride> overrides = new HashMap<>();
overrides.put("attack-frequency", PlayerModuleOverride.FORCE_ENABLED);
overrides.put("sword-blocking", PlayerModuleOverride.FORCE_ENABLED);
overrides.put("disable-offhand", PlayerModuleOverride.FORCE_DISABLED);
api.setModuleOverridesForPlayer(player, overrides);
Kotlin:
api.setModuleOverridesForPlayer(
player,
mapOf(
"attack-frequency" to PlayerModuleOverride.FORCE_ENABLED,
"sword-blocking" to PlayerModuleOverride.FORCE_ENABLED,
"disable-offhand" to PlayerModuleOverride.FORCE_DISABLED,
),
)
isModuleEnabledForPlayer
Returns whether a configurable module is effectively active for a player, accounting for configured rules and any per-player override.
boolean isModuleEnabledForPlayer(Player player, String moduleName)
Throws: IllegalArgumentException if moduleName is unknown or non-configurable.
if (api.isModuleEnabledForPlayer(player, "attack-frequency")) {
// module is active for this player
}
Kotlin:
if (api.isModuleEnabledForPlayer(player, "attack-frequency")) {
// module is active for this player
}
hasAnyOverrideForPlayer
Returns whether a player has any non-default override set.
boolean hasAnyOverrideForPlayer(Player player)
if (api.hasAnyOverrideForPlayer(player)) {
player.sendMessage("You have active overrides.");
}
Kotlin:
if (api.hasAnyOverrideForPlayer(player)) {
player.sendMessage("You have active overrides.")
}
getModuleNames
Returns the names of all configurable modules that support per-player overrides. Only names in this set are valid inputs for the other API methods.
Set<String> getModuleNames()
for (String name : api.getModuleNames()) {
player.sendMessage(name);
}
Kotlin:
api.getModuleNames().forEach { name ->
player.sendMessage(name)
}
PlayerModuleOverride
| Value | Description |
|---|---|
DEFAULT |
No per-player override; the player follows configured module and modeset rules. |
FORCE_ENABLED |
Module is forced on for the player before configured disabled modules are considered. |
FORCE_DISABLED |
Module is forced off for the player before configured enabled modules are considered. |
Events
Register event listeners with Bukkit as usual. Events are fired after OCM changes the stored API state and before it reapplies module state for the affected player.
PlayerModesetChangeEvent
Fired when a player's stored modeset changes. Reasons are API, COMMAND, WORLD_CHANGE, and JOIN.
@EventHandler
public void onModesetChange(PlayerModesetChangeEvent event) {
Player player = event.getPlayer();
String previous = event.getPreviousModeset();
String current = event.getNewModeset();
player.sendMessage("Modeset changed from " + previous + " to " + current
+ " because of " + event.getReason());
}
getPreviousModeset() and getNewModeset() can return null when no modeset was previously stored or no replacement is available.
PlayerModuleOverrideChangeEvent
Fired when a player's per-player module override changes.
@EventHandler
public void onModuleOverrideChange(PlayerModuleOverrideChangeEvent event) {
if (event.getNewOverride() == PlayerModuleOverride.DEFAULT) {
event.getPlayer().sendMessage(event.getModuleName() + " is following configured rules again.");
return;
}
event.getPlayer().sendMessage(event.getModuleName() + " changed from "
+ event.getPreviousOverride() + " to " + event.getNewOverride());
}
Java example
public class ArenaCombatManager {
private final OldCombatMechanicsAPI api;
private static final Map<String, PlayerModuleOverride> ARENA_OVERRIDES;
static {
ARENA_OVERRIDES = new HashMap<>();
ARENA_OVERRIDES.put("attack-frequency", PlayerModuleOverride.FORCE_ENABLED);
ARENA_OVERRIDES.put("sword-blocking", PlayerModuleOverride.FORCE_ENABLED);
ARENA_OVERRIDES.put("disable-offhand", PlayerModuleOverride.FORCE_ENABLED);
}
public ArenaCombatManager(OldCombatMechanicsAPI api) {
this.api = api;
}
public void onPlayerJoinArena(Player player) {
api.setModuleOverridesForPlayer(player, ARENA_OVERRIDES);
}
public void onPlayerLeaveArena(Player player) {
api.clearAllModuleOverridesForPlayer(player);
}
}
Kotlin example
class ArenaCombatManager(private val api: OldCombatMechanicsAPI) {
private val arenaOverrides = mapOf(
"attack-frequency" to PlayerModuleOverride.FORCE_ENABLED,
"sword-blocking" to PlayerModuleOverride.FORCE_ENABLED,
"disable-offhand" to PlayerModuleOverride.FORCE_ENABLED,
)
fun onPlayerJoinArena(player: Player) {
api.setModuleOverridesForPlayer(player, arenaOverrides)
}
fun onPlayerLeaveArena(player: Player) {
api.clearAllModuleOverridesForPlayer(player)
}
}