⚔️ Sistema de Combate
Guía completa del sistema de combate de Hytale: cálculo de daño, tipos de daño, knockback, armadura, stamina, bloqueo e invulnerabilidad.
🎯 Mecánicas de Combate
| Mecánica | Descripción | Archivo Clave |
|---|---|---|
| 💥 Daño Base | DPS o ABSOLUTO con variación aleatoria | DamageCalculator.java |
| 🛡️ Reducción por Armadura | Modificadores ADITIVO y MULTIPLICATIVO | DamageEntityInteraction.java |
| 🔄 Knockback | 3 tipos: Directional, Point, Force | Knockback.java |
| ⚡ Stamina | Consumo por bloquear/wield, stamina broken | StaminaModule.java |
| 🛡️ Bloqueo | WieldingInteraction con BlockedEffects | WieldingInteraction.java |
| ✨ Invulnerabilidad | Componente Invulnerable, iframes por efectos | InvulnerableSystems.java |
| 📐 Daño Angular | Diferente daño según ángulo de ataque | AngledDamage |
| 🎯 Daño por Zona | Headshots, weak points específicos | TargetedDamage |
💥 Cálculo de Daño
Fórmula Principal
// Fórmula simplificada del daño
dañoFinal = (dañoBase + armorFlatModifier) * armorMultiplierModifier * randomModifier
// Donde:
dañoBase = baseDamage * (tipo == DPS ? duración : 1.0)
randomModifier = 1.0 + random(-randomPercentageModifier, +randomPercentageModifier)
armorFlatModifier = suma de todos los ADDITIVE de armadura
armorMultiplierModifier = producto de todos los MULTIPLICATIVE de armadura
DamageCalculator
public class DamageCalculator {
enum Type {
DPS, // Daño = baseDamage * duración
ABSOLUTE // Daño = baseDamage fijo
}
DamageClass damageClass; // MELEE, RANGED, etc.
Map<DamageCause, Float> baseDamage; // {"Physical": 10.0}
float sequentialModifierStep; // Reducción por hits secuenciales
float sequentialModifierMinimum; // Mínimo del modificador
float randomPercentageModifier; // Variación ±X%
}
Sequential Modifier
Reduce el daño en ataques rápidos consecutivos. Ejemplo: si step=0.1 y minimum=0.5, el primer hit hace 100%, segundo 90%, tercero 80%... hasta mínimo 50%.
Reduce el daño en ataques rápidos consecutivos. Ejemplo: si step=0.1 y minimum=0.5, el primer hit hace 100%, segundo 90%, tercero 80%... hasta mínimo 50%.
🏷️ Tipos de Daño (DamageCause)
public class DamageCause {
String id; // "Physical", "Fall", "Drowning"
boolean durabilityLoss; // ¿Daña durabilidad del arma?
boolean staminaLoss; // ¿Consume stamina al bloquear?
boolean bypassResistances;// ¿Ignora resistencias?
String damageTextColor; // Color del número de daño
String animationId; // Animación al recibir daño
String deathAnimationId; // Animación de muerte
}
Tipos Predefinidos
| Tipo | Durability | Stamina | Bypass | Color |
|---|---|---|---|---|
| Physical | ✅ | ✅ | ❌ | Rojo |
| Projectile | ✅ | ✅ | ❌ | Naranja |
| Fall | ❌ | ❌ | ❌ | Blanco |
| Drowning | ❌ | ❌ | ❌ | Azul |
| Environment | ❌ | ❌ | ✅ | Gris |
| Command | ❌ | ❌ | ✅ | Púrpura |
🔄 Sistema de Knockback
Tipos de Knockback
abstract class Knockback {
float force; // Fuerza del empuje
float duration; // Duración (0 = instantáneo)
ChangeVelocityType velocityType; // Add, Set, Scale
abstract Vector3d calculateVector(attackerPos, yaw, targetPos);
}
// 3 Implementaciones:
class DirectionalKnockback // Dirección fija (hacia donde mira)
class PointKnockback // Desde punto de impacto
class ForceKnockback // Fuerza personalizada
Modificadores de Knockback
// El knockback se modifica por:
knockbackFinal = knockbackBase * effectMultiplier * armorEnhancement
// effectMultiplier: multiplicador de efectos activos
// armorEnhancement: bonus/reducción por armadura
🛡️ Sistema de Armadura
Modificadores de Armadura
// La armadura tiene dos tipos de modificadores:
enum CalculationType {
ADDITIVE, // Suma/resta al daño base
MULTIPLICATIVE // Multiplica el daño final
}
// Ejemplo: Armadura con -5 ADDITIVE y 0.8 MULTIPLICATIVE
dañoFinal = (20 + (-5)) * 0.8 = 12
Cálculo de Reducción
for (ItemStack armorPiece : armorSlots) {
if (armorPiece.hasArmor()) {
// Aplicar modificadores por tipo de daño
StaticModifier[] mods = armor.getDamageEnhancementValues().get(damageCause);
for (StaticModifier mod : mods) {
if (mod.getCalculationType() == ADDITIVE) {
flatModifier += mod.getAmount();
} else {
multiplierModifier += mod.getAmount();
}
}
// Aplicar modificadores por clase de daño (MELEE, RANGED...)
StaticModifier[] classMods = armor.getDamageClassEnhancement().get(damageClass);
// ... mismo proceso
}
}
⚡ Sistema de Stamina
Consumo de Stamina al Bloquear
class StaminaCost {
enum CostType {
MAX_HEALTH_PERCENTAGE, // 1 stamina = X% de salud máxima
DAMAGE // 1 stamina = X puntos de daño
}
CostType costType = MAX_HEALTH_PERCENTAGE;
float value = 0.04f; // 4% por defecto
float computeStaminaAmountToConsume(damageRaw, entityStatMap) {
return costType == DAMAGE
? damageRaw / value
: damageRaw / (entityStatMap.getMaxHealth() * value);
}
}
Stamina Broken Effect
// Cuando la stamina llega a 0 al bloquear:
if (staminaAfterBlock <= 0) {
applyEffect(combatConfig.getStaminaBrokenEffectId()); // "Stamina_Broken"
// El jugador queda vulnerable por un tiempo
}
Stamina Drain Multiplier
Los efectos de daño pueden tener
Los efectos de daño pueden tener
staminaDrainMultiplier para aumentar
el consumo de stamina del defensor. Valor por defecto: 1.0
🛡️ Sistema de Bloqueo (Wielding)
class WieldingInteraction {
Map<DamageCause, StaticModifier[]> damageModifiers; // Reducción de daño
Map<DamageCause, Float> knockbackModifiers; // Reducción de knockback
AngledWielding[] angledWielding; // Bloqueo direccional
StaminaCost staminaCost; // Consumo de stamina
DamageEffects blockedEffects; // Efectos al bloquear
String failed; // Interacción si falla
}
Flujo de Bloqueo
// 1. Verificar si el jugador está en posición de bloqueo
if (isWielding \u0026\u0026 !staminaBroken) {
// 2. Calcular ángulo entre atacante y defensor
float angleBetween = calculateAngle(attacker, defender);
// 3. Verificar si el ataque viene de frente (ángulo válido)
if (isWithinBlockAngle(angleBetween, wieldingConfig)) {
// 4. Aplicar reducción de daño
damage.setAmount(damage.getAmount() * blockMultiplier);
damage.putMetaObject(Damage.BLOCKED, true);
// 5. Consumir stamina
staminaToConsume = staminaCost.compute(damage);
entityStats.subtract(STAMINA, staminaToConsume);
// 6. Aplicar efectos de bloqueo (sonido, partículas)
blockedEffects.addToDamage(damage);
}
}
✨ Sistema de Invulnerabilidad
Componente Invulnerable
// Una entidad es invulnerable si:
boolean isInvulnerable =
archetype.contains(Invulnerable.getComponentType()) || // Componente fijo
effectController.isInvulnerable(); // Efecto temporal
// Verificación antes de aplicar daño
if (invulnerable \u0026\u0026 !damageCause.doesBypassResistances()) {
damage.setCancelled(true);
return;
}
Invulnerabilidad por Efectos
// Los EntityEffects pueden dar iframes
class EntityEffect {
ApplicationEffects applicationEffects;
class ApplicationEffects {
boolean invulnerable; // El efecto da invulnerabilidad
float knockbackMultiplier; // Modificador de knockback
}
}
// El sistema combina todos los efectos activos
float knockbackMult = activeEffects.stream()
.filter(e -\u003e e.getApplicationEffects() != null)
.mapToDouble(e -\u003e e.getApplicationEffects().getKnockbackMultiplier())
.reduce(1.0, (a, b) -\u003e a * b);
📐 Daño Angular y Dirigido
AngledDamage (Golpes Traseros)
class AngledDamage extends TargetedDamage {
float angleRad; // Ángulo central (ej: 180° = espalda)
float angleDistanceRad; // Tolerancia (ej: ±30°)
DamageCalculator damageCalculator; // Daño específico
DamageEffects damageEffects; // Efectos específicos
}
// Ejemplo: Backstab con 50% más daño
{
"Angle": 180,
"AngleDistance": 45,
"DamageCalculator": {
"Type": "ABSOLUTE",
"BaseDamage": { "Physical": 15.0 } // vs 10.0 normal
}
}
TargetedDamage (Headshots)
// Daño diferente según la zona del hitbox
"TargetedDamage": {
"Head": {
"DamageCalculator": {
"BaseDamage": { "Physical": 25.0 } // 2.5x daño
},
"DamageEffects": {
"CameraEffectId": "HeadshotEffect"
}
},
"Torso": {
"DamageCalculator": {
"BaseDamage": { "Physical": 10.0 }
}
}
}
⚙️ Configuración Global de Combate
class CombatConfig {
Duration outOfCombatDelay = 5000ms; // Tiempo para salir de combate
String staminaBrokenEffectId = "Stamina_Broken";
boolean displayHealthBars = true; // Mostrar barras de vida
boolean displayCombatText = true; // Mostrar números de daño
boolean disableNpcIncomingDamage = false; // NPCs invulnerables
boolean disablePlayerIncomingDamage = false; // Jugadores invulnerables
}
📊 EntityStatOnHit (Lifesteal, etc.)
class EntityStatOnHit {
String entityStatId; // Ej: "Health" para lifesteal
float amount; // Cantidad base
float[] multipliersPerEntitiesHit = {1.0, 0.6, 0.4, 0.2, 0.1};
float multiplierPerExtraEntityHit = 0.05;
}
// Ejemplo: Lifesteal que da 5 HP por hit
// 1 enemigo: 5 * 1.0 = 5 HP
// 2 enemigos: 5 * 0.6 = 3 HP cada uno
// 6+ enemigos: 5 * 0.05 = 0.25 HP cada uno
📁 Archivos Clave
- DamageEntityInteraction.java 551 líneas - Interacción principal de ataque
- DamageSystems.java 1451 líneas - Sistema principal de daño
- DamageCalculator.java 171 líneas - Cálculo de daño
- Damage.java 287 líneas - Evento de daño
- DamageCause.java 149 líneas - Tipos de daño
- Knockback.java 62 líneas - Sistema de knockback
- WieldingInteraction.java Sistema de bloqueo y wielding
- CombatConfig.java 78 líneas - Configuración global
- StaminaModule.java Sistema de stamina