Skip to content

Charging Mechanics

Charging interactions allow players to hold a button to charge up an action, then release to execute based on charge level.

ChargingInteraction extends the basic interaction system with:

  • Multi-tier charging - Different effects at different charge levels
  • Visual progress - UI progress bar during charge
  • Damage cancellation - Cancel charge if player takes damage
  • Forking during charge - Execute actions while holding (e.g., shield bash)
  • Mouse sensitivity - Reduced turn rate while charging
Simple charging interaction
{
"Type": "Charging",
"DisplayProgress": true,
"FailOnDamage": true,
"Next": {
"0.5": "Quick_Release",
"1.5": "Medium_Charge",
"3.0": "Full_Charge"
},
"Failed": "Charge_Interrupted"
}
FieldTypeDescription
DisplayProgressboolShow charge progress bar in UI
FailOnDamageboolCancel if player takes damage
NextobjectMap of charge thresholds to interactions
FailedstringInteraction to run if charge cancelled
DelayobjectDelay configuration based on entity health

The Next field maps charge duration (in seconds) to interactions:

{
"Next": {
"0.0": "Instant_Release", // Immediate release (tap)
"0.5": "Quick_Attack", // Half second charge
"1.5": "Charged_Attack", // 1.5 second charge
"3.0": "Maximum_Charge" // Full charge
}
}
// From ChargingInteraction.java - simplified
private float jumpToChargeValue(float chargeValue) {
float closestKey = 0.0f;
float closestDiff = Float.MAX_VALUE;
// Find closest tier at or below current charge
for (float tier : next.keySet()) {
if (tier <= chargeValue) {
float diff = chargeValue - tier;
if (diff < closestDiff) {
closestDiff = diff;
closestKey = tier;
}
}
}
return closestKey;
}

When the player releases, the system finds the highest tier reached and executes that interaction.

Allow additional inputs while charging (e.g., shield bash while blocking):

Charging with forks
{
"Type": "Charging",
"DisplayProgress": true,
"Next": {
"2.0": "Bow_Fire_Weak",
"4.0": "Bow_Fire_Full"
},
"Forks": {
"Secondary": "Root_Bow_Cancel",
"Ability1": "Root_Special_Arrow"
}
}
Fork KeyWhen Triggered
PrimaryLeft click while charging
SecondaryRight click while charging
Ability1-3Ability keys while charging
// From ChargingInteraction - fork handling
if (forks != null && type != InteractionType.Secondary) {
InteractionType[] forkTypes = InteractionType.VALUES;
for (InteractionType forkType : forkTypes) {
String forkInteraction = forks.get(forkType);
if (forkInteraction != null) {
// Check if fork was triggered
if (forkTriggered(context, forkType)) {
RootInteraction forkRoot = RootInteraction
.getRootInteractionOrUnknown(forkInteraction);
context.fork(context.duplicate(), forkRoot, true);
}
}
}
}

When FailOnDamage is enabled, taking damage interrupts the charge:

{
"Type": "Charging",
"FailOnDamage": true,
"Delay": {
"MinDelay": 0.1,
"MaxDelay": 0.5,
"MaxTotalDelay": 1.0,
"MinHealth": 0.2,
"MaxHealth": 0.8
},
"Failed": "Charge_Cancelled"
}

Damage cancellation delay varies based on current health:

FieldDescription
MinDelayDelay at MaxHealth ratio (short delay when healthy)
MaxDelayDelay at MinHealth ratio (long delay when hurt)
MaxTotalDelayCaps the total computed delay value
MinHealthHealth ratio for maximum delay
MaxHealthHealth ratio for minimum delay

At full health, there’s less delay before damage cancels the charge. When low on health, there’s more delay (giving defensive players a buffer).

WieldingInteraction extends ChargingInteraction for blocking/guarding:

Shield block with wielding
{
"Type": "Wielding",
"DisplayProgress": false,
"RunTime": 0.0,
"DamageModifiers": {
"Physical": 0.3,
"Fire": 0.5
},
"KnockbackModifiers": {
"Physical": 0.2
},
"BlockedEffects": {
"WorldSoundEventId": "SFX_Shield_Block",
"CameraEffect": "Shield_Impact"
},
"BlockedInteractions": "Root_Shield_Blocked",
"StaminaCost": {
"CostType": "MAX_HEALTH_PERCENTAGE",
"Value": 0.04
},
"Forks": {
"Primary": "Root_Shield_Bash"
}
}
FieldTypeDescription
DamageModifiersobjectDamage reduction per damage type (0.3 = 30% damage)
KnockbackModifiersobjectKnockback reduction per damage cause
BlockedEffectsobjectEffects when successfully blocking
BlockedInteractionsstringRootInteraction on successful block
StaminaCostobjectStamina drain configuration
AngledWieldingobjectDirection-based blocking

Block effectiveness varies based on attack direction:

{
"AngledWielding": {
"Angle": 90,
"AngleDistance": 45,
"KnockbackModifiers": {
"Physical": 0.5
},
"DamageModifiers": {
"Physical": 0.6
}
}
}

Attacks from within the blocking angle get the main modifiers. Attacks from outside use the AngledWielding modifiers (worse protection).

{
"StaminaCost": {
"CostType": "MAX_HEALTH_PERCENTAGE",
"Value": 0.04
}
}
CostTypeDescription
MAX_HEALTH_PERCENTAGEStamina cost = damage / (Value * MaxHealth)
DAMAGEStamina cost = damage / Value

When stamina reaches zero, the block breaks and the player is staggered.

Charging reduces mouse sensitivity for aim stability. This is controlled by two fields on ChargingInteraction:

{
"Type": "Charging",
"MouseSensitivityAdjustmentTarget": 0.4,
"MouseSensitivityAdjustmentDuration": 0.3
}
FieldTypeDefaultDescription
MouseSensitivityAdjustmentTargetfloat1.0Target sensitivity multiplier (lower = less sensitive)
MouseSensitivityAdjustmentDurationfloat1.0Time in seconds to ease into the adjusted sensitivity

These fields also exist on ApplicationEffects, so they can be set inside Effects as well. Useful for:

  • Bow aiming
  • Heavy attack wind-ups
  • Channeled spells

Charging uses WaitForDataFrom.Client - the server waits for the client to report when the button was released:

// From ChargingInteraction.java
@Override
public WaitForDataFrom getWaitForDataFrom() {
return WaitForDataFrom.Client;
}
@Override
protected void tick0(...) {
InteractionSyncData clientData = context.getClientState();
if (clientData == null) {
// Still waiting for client data
context.getState().state = InteractionState.NotFinished;
return;
}
// Use client's charge value
float chargeValue = clientData.chargeValue;
float selectedTier = jumpToChargeValue(chargeValue);
context.jump(context.getLabel(tierIndex));
}
// InteractionSyncData fields for charging
public float chargeValue; // How long charged
public Map<InteractionType, Integer> forkCounts; // Fork triggers
public InteractionState state; // Finished/Failed
Root_Bow_Primary.json
{
"RequireNewClick": true,
"Cooldown": { "Cooldown": 0.2 },
"Interactions": ["Bow_Draw"]
}
Bow_Draw.json
{
"Type": "Charging",
"DisplayProgress": true,
"FailOnDamage": false,
"MouseSensitivityAdjustmentTarget": 0.6,
"MouseSensitivityAdjustmentDuration": 0.3,
"Effects": {
"ItemAnimationId": "Draw",
"WorldSoundEventId": "SFX_Bow_Draw"
},
"Next": {
"0.0": "Bow_Release_Quick",
"0.5": "Bow_Release_Half",
"1.0": "Bow_Release_Full"
},
"Forks": {
"Secondary": "Root_Bow_Cancel"
}
}
Bow_Release_Full.json
{
"Type": "Serial",
"Interactions": [
{
"Type": "Simple",
"RunTime": 0.1,
"Effects": {
"ItemAnimationId": "Release",
"WorldSoundEventId": "SFX_Bow_Release"
}
},
{
"Type": "SpawnProjectile",
"ProjectileId": "Arrow_Standard",
"SpeedMultiplier": 1.5,
"DamageMultiplier": 2.0
}
]
}
Root_Shield_Guard.json
{
"RequireNewClick": false,
"Interactions": ["Shield_Guard"]
}
Shield_Guard.json
{
"Type": "Wielding",
"DisplayProgress": false,
"DamageModifiers": {
"Physical": 0.2,
"Fire": 0.4,
"Ice": 0.4
},
"KnockbackModifiers": {
"Physical": 0.1,
"Explosive": 0.3
},
"MouseSensitivityAdjustmentTarget": 0.8,
"MouseSensitivityAdjustmentDuration": 0.2,
"Effects": {
"ItemAnimationId": "Guard"
},
"BlockedEffects": {
"WorldSoundEventId": "SFX_Shield_Block",
"WorldParticles": [{ "SystemId": "Shield_Spark" }],
"CameraEffect": "Shield_Hit"
},
"BlockedInteractions": "Root_Shield_Stagger",
"StaminaCost": {
"CostType": "MAX_HEALTH_PERCENTAGE",
"Value": 0.05
},
"AngledWielding": {
"Angle": 120,
"AngleDistance": 60,
"DamageModifiers": {
"Physical": 0.6
}
},
"Forks": {
"Primary": "Root_Shield_Bash"
},
"Next": {
"0.0": "Shield_Lower"
},
"Failed": "Shield_Break"
}
Interaction (base)
└── ChargingInteraction
└── WieldingInteraction

Both charging and wielding share the same core mechanics, with wielding adding damage reduction and stamina systems.