Go back to constant electrocution damage based on voltage (#26455)
Makes electrocution damage based on the voltage of the wire and bring down the damage to a sane level. It's no longer primarily based on the power being received. LV Cable -> 10 damage MV Cable -> 20 damage HV Cable -> 30 damage Having a primarily power-based damage system causes there to be massive fluctuations in damage based on things outside of a regular player's control, like the station power output. This removes a lot of player agency and turns grilles into a risky gamble where they can either do no damage or instantly fry the player due to simply being hooked up to the engine. While this may be a more accurate simulation in some regards, the reality of the gameplay is that it's often just frustrating, resulting in constant death traps as players brushing against electrified grilles and punching wires take absurd amounts of damage. By making them flat rates, it's possible to actually balance the damage output.
This commit is contained in:
@@ -41,8 +41,32 @@ public sealed partial class ElectrifiedComponent : Component
|
|||||||
[DataField("lowVoltageNode")]
|
[DataField("lowVoltageNode")]
|
||||||
public string? LowVoltageNode;
|
public string? LowVoltageNode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Damage multiplier for HV electrocution
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float HighVoltageDamageMultiplier = 3f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shock time multiplier for HV electrocution
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float HighVoltageTimeMultiplier = 1.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Damage multiplier for MV electrocution
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float MediumVoltageDamageMultiplier = 2f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shock time multiplier for MV electrocution
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float MediumVoltageTimeMultiplier = 1.25f;
|
||||||
|
|
||||||
[DataField("shockDamage")]
|
[DataField("shockDamage")]
|
||||||
public int ShockDamage = 20;
|
public float ShockDamage = 7.5f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shock time, in seconds.
|
/// Shock time, in seconds.
|
||||||
|
|||||||
@@ -15,10 +15,4 @@ public sealed partial class ElectrocutionComponent : Component
|
|||||||
|
|
||||||
[DataField("timeLeft")]
|
[DataField("timeLeft")]
|
||||||
public float TimeLeft;
|
public float TimeLeft;
|
||||||
|
|
||||||
[DataField("accumDamage")]
|
|
||||||
public float AccumulatedDamage;
|
|
||||||
|
|
||||||
[DataField("baseDamage")]
|
|
||||||
public float BaseDamage = 20f;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ using Content.Shared.Interaction;
|
|||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Jittering;
|
using Content.Shared.Jittering;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Content.Shared.Mobs;
|
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Speech.EntitySystems;
|
using Content.Shared.Speech.EntitySystems;
|
||||||
using Content.Shared.StatusEffect;
|
using Content.Shared.StatusEffect;
|
||||||
@@ -98,29 +97,18 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
private void UpdateElectrocutions(float frameTime)
|
private void UpdateElectrocutions(float frameTime)
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<ElectrocutionComponent, PowerConsumerComponent>();
|
var query = EntityQueryEnumerator<ElectrocutionComponent, PowerConsumerComponent>();
|
||||||
while (query.MoveNext(out var uid, out var electrocution, out var consumer))
|
while (query.MoveNext(out var uid, out var electrocution, out _))
|
||||||
{
|
{
|
||||||
var timePassed = Math.Min(frameTime, electrocution.TimeLeft);
|
var timePassed = Math.Min(frameTime, electrocution.TimeLeft);
|
||||||
|
|
||||||
electrocution.TimeLeft -= timePassed;
|
electrocution.TimeLeft -= timePassed;
|
||||||
electrocution.AccumulatedDamage += electrocution.BaseDamage * (consumer.ReceivedPower / consumer.DrawRate) * timePassed;
|
|
||||||
|
|
||||||
if (!MathHelper.CloseTo(electrocution.TimeLeft, 0))
|
if (!MathHelper.CloseTo(electrocution.TimeLeft, 0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (EntityManager.EntityExists(electrocution.Electrocuting))
|
// We tried damage scaling based on power in the past and it really wasn't good.
|
||||||
{
|
// Various scaling types didn't fix tiders and HV grilles instantly critting players.
|
||||||
// TODO: damage should be scaled by shock damage multiplier
|
|
||||||
// TODO: better paralyze/jitter timing
|
|
||||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType), (int) electrocution.AccumulatedDamage);
|
|
||||||
|
|
||||||
var actual = _damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source);
|
|
||||||
if (actual != null)
|
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Electrocution,
|
|
||||||
$"{ToPrettyString(electrocution.Electrocuting):entity} received {actual.GetTotal():damage} powered electrocution damage from {ToPrettyString(electrocution.Source):source}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QueueDel(uid);
|
QueueDel(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +186,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
if (!_meleeWeapon.GetDamage(args.Used, args.User).Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false, 1);
|
DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
||||||
@@ -213,16 +201,6 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
TryDoElectrifiedAct(uid, args.User, siemens, electrified);
|
TryDoElectrifiedAct(uid, args.User, siemens, electrified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateElectrifiedDamageScale(float power)
|
|
||||||
{
|
|
||||||
// A logarithm allows a curve of damage that grows quickly, but slows down dramatically past a value. This keeps the damage to a reasonable range.
|
|
||||||
const float DamageShift = 1.67f; // Shifts the curve for an overall higher or lower damage baseline
|
|
||||||
const float CeilingCoefficent = 1.35f; // Adjusts the approach to maximum damage, higher = Higher top damage
|
|
||||||
const float LogGrowth = 0.00001f; // Adjusts the growth speed of the curve
|
|
||||||
|
|
||||||
return DamageShift + MathF.Log(power * LogGrowth) * CeilingCoefficent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
||||||
float siemens = 1,
|
float siemens = 1,
|
||||||
ElectrifiedComponent? electrified = null,
|
ElectrifiedComponent? electrified = null,
|
||||||
@@ -263,19 +241,15 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var node = PoweredNode(uid, electrified, nodeContainer);
|
var node = PoweredNode(uid, electrified, nodeContainer);
|
||||||
if (node?.NodeGroup is not IBasePowerNet powerNet)
|
if (node?.NodeGroup is not IBasePowerNet)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var net = powerNet.NetworkNode;
|
var (damageScalar, timeScalar) = node.NodeGroupID switch
|
||||||
var supp = net.LastCombinedMaxSupply;
|
{
|
||||||
|
NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier),
|
||||||
if (supp <= 0f)
|
NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier, electrified.MediumVoltageTimeMultiplier),
|
||||||
return false;
|
_ => (1f, 1f)
|
||||||
|
};
|
||||||
// Initial damage scales off of the available supply on the principle that the victim has shorted the entire powernet through their body.
|
|
||||||
var damageScale = CalculateElectrifiedDamageScale(supp);
|
|
||||||
if (damageScale <= 0f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var lastRet = true;
|
var lastRet = true;
|
||||||
@@ -286,8 +260,8 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
|||||||
entity,
|
entity,
|
||||||
uid,
|
uid,
|
||||||
node,
|
node,
|
||||||
(int) MathF.Ceiling(electrified.ShockDamage * damageScale * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageScalar),
|
||||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Min(1f + MathF.Log2(1f + damageScale), 3f) * MathF.Pow(RecursiveTimeMultiplier, depth)),
|
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) * timeScalar),
|
||||||
true,
|
true,
|
||||||
electrified.SiemensCoefficient);
|
electrified.SiemensCoefficient);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user