Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

Advanced Combat Systems
AdvancedChapter 2 of 325 min read

Damage Resolution Pipeline

The damage resolution pipeline takes a raw attack and calculates the final damage after range falloff, resistances, and shield/armor/hull layers.

The Pipeline

``

Raw Damage → Range Modifier → Resistance → Layer Application → Final Result

``

Range Falloff

Weapons have an optimal range and a falloff range. Damage is full within optimal, then decreases:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

library DamageCalculation {
  /// @notice Calculate range-modified damage
  /// @param baseDamage The weapon's base damage
  /// @param distance Distance to target
  /// @param optimalRange Full damage range
  /// @param falloffRange Range over which damage drops to ~0
  function applyRangeFalloff(
    uint32 baseDamage,
    uint32 distance,
    uint32 optimalRange,
    uint32 falloffRange
  ) internal pure returns (uint32) {
    if (distance <= optimalRange) {
      return baseDamage;
    }

    if (falloffRange == 0) {
      return distance <= optimalRange ? baseDamage : 0;
    }

    uint32 excessRange = distance - optimalRange;
    if (excessRange >= falloffRange * 3) {
      return 0; // Beyond 3x falloff = no damage
    }

    // Linear falloff for simplicity
    // Production systems might use exponential curves
    uint32 falloffMultiplier = ((falloffRange  3 - excessRange)  10000)
      / (falloffRange * 3);
    return (baseDamage * falloffMultiplier) / 10000;
  }

  /// @notice Apply resistance to damage
  /// @param damage Pre-resistance damage
  /// @param resistance Resistance in basis points (10000 = 100%)
  function applyResistance(
    uint32 damage,
    uint16 resistance
  ) internal pure returns (uint32) {
    if (resistance >= 10000) return 0;
    return damage - ((damage * resistance) / 10000);
  }
}

Shield, Armor, Hull Layers

Damage flows through layers in order. Each layer absorbs what it can:

solidity
struct DamageResult {
  uint32 shieldDamage;
  uint32 armorDamage;
  uint32 hullDamage;
  bool destroyed;
}

function applyDamageToLayers(
  uint32 damage,
  uint32 shieldHp,
  uint32 armorHp,
  uint32 hullHp
) internal pure returns (DamageResult memory) {
  DamageResult memory result;
  uint32 remaining = damage;

  // Shields absorb first
  if (remaining <= shieldHp) {
    result.shieldDamage = remaining;
    return result;
  }
  result.shieldDamage = shieldHp;
  remaining -= shieldHp;

  // Then armor
  if (remaining <= armorHp) {
    result.armorDamage = remaining;
    return result;
  }
  result.armorDamage = armorHp;
  remaining -= armorHp;

  // Finally hull
  result.hullDamage = remaining > hullHp ? hullHp : remaining;
  result.destroyed = remaining >= hullHp;

  return result;
}

Full Resolution

Putting it all together:

solidity
function resolveAttack(
  uint256 attackerShipId,
  uint8 weaponSlot,
  uint256 targetShipId,
  uint32 distance
) public returns (DamageResult memory) {
  // 1. Get weapon config
  WeaponConfig memory weapon = WeaponConfig.get(
    getWeaponId(attackerShipId, weaponSlot)
  );

  // 2. Check cooldown
  uint256 lastFired = WeaponCooldown.getLastFiredAt(
    attackerShipId, weaponSlot
  );
  require(
    block.timestamp >= lastFired + weapon.cooldownSeconds,
    "Weapon on cooldown"
  );

  // 3. Apply range falloff
  uint32 rangeDamage = DamageCalculation.applyRangeFalloff(
    weapon.baseDamage, distance,
    weapon.optimalRange, weapon.falloffRange
  );

  // 4. Get target resistances
  ShipDefenses memory defenses = ShipDefenses.get(targetShipId);
  uint16 resistance = getResistanceForType(
    defenses, weapon.damageType
  );

  // 5. Apply resistance
  uint32 finalDamage = DamageCalculation.applyResistance(
    rangeDamage, resistance
  );

  // 6. Apply to layers
  DamageResult memory result = applyDamageToLayers(
    finalDamage,
    defenses.shieldHp, defenses.armorHp, defenses.hullHp
  );

  // 7. Update state
  WeaponCooldown.setLastFiredAt(
    attackerShipId, weaponSlot, block.timestamp
  );
  updateShipHp(targetShipId, result);

  return result;
}

In the final chapter, we'll add events, logging, and integration patterns.

Sign in to track your progress.