private void PickEvent(DamageSpecifier ds, bool random) { List <DamageActions> vs = new List <DamageActions>(); if ((ds.shipDamageEvent & DamageSpecifier.ShipDamageEvent.ripSail) == DamageSpecifier.ShipDamageEvent.ripSail) { vs.Add(DamageActions.sail); } if ((ds.shipDamageEvent & DamageSpecifier.ShipDamageEvent.breakRudder) == DamageSpecifier.ShipDamageEvent.breakRudder) { vs.Add(DamageActions.rudder); } if ((ds.shipDamageEvent & DamageSpecifier.ShipDamageEvent.knockOutCaptain) == DamageSpecifier.ShipDamageEvent.knockOutCaptain) { vs.Add(DamageActions.captain); } if ((ds.shipDamageEvent & DamageSpecifier.ShipDamageEvent.blowOutLantern) == DamageSpecifier.ShipDamageEvent.blowOutLantern) { vs.Add(DamageActions.lantern); } if (random) { var choice = (int)UnityEngine.Random.Range(0f, vs.Count - 0.01f); damageActions[choice].Invoke(); } else { foreach (var choice in vs) { damageActions[(int)choice].Invoke(); } } }
private void OnClickAttack(EntityUid owner, MeleeWeaponComponent comp, ClickAttackEvent args) { args.Handled = true; var curTime = _gameTiming.CurTime; if (curTime < comp.CooldownEnd || args.Target == null) { return; } var location = EntityManager.GetComponent <TransformComponent>(args.User).Coordinates; var diff = args.ClickLocation.ToMapPos(EntityManager) - location.ToMapPos(EntityManager); var angle = Angle.FromWorldVec(diff); if (args.Target is { Valid : true } target) { // Raise event before doing damage so we can cancel damage if the event is handled var hitEvent = new MeleeHitEvent(new List <EntityUid>() { target }, args.User); RaiseLocalEvent(owner, hitEvent, false); if (!hitEvent.Handled) { var targets = new[] { target }; SendAnimation(comp.ClickArc, angle, args.User, owner, targets, comp.ClickAttackEffect, false); RaiseLocalEvent(target, new AttackedEvent(args.Used, args.User, args.ClickLocation)); var modifiedDamage = DamageSpecifier.ApplyModifierSets(comp.Damage + hitEvent.BonusDamage, hitEvent.ModifiersList); var damageResult = _damageableSystem.TryChangeDamage(target, modifiedDamage); if (damageResult != null) { if (args.Used == args.User) { _logSystem.Add(LogType.MeleeHit, $"{ToPrettyString(args.User):user} melee attacked {ToPrettyString(args.Target.Value):target} using their hands and dealt {damageResult.Total:damage} damage"); } else { _logSystem.Add(LogType.MeleeHit, $"{ToPrettyString(args.User):user} melee attacked {ToPrettyString(args.Target.Value):target} using {ToPrettyString(args.Used):used} and dealt {damageResult.Total:damage} damage"); } } if (hitEvent.HitSoundOverride != null) { SoundSystem.Play(Filter.Pvs(owner), hitEvent.HitSoundOverride.GetSound(), target, AudioHelpers.WithVariation(0.25f)); } else { SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target); } } }
private bool TryParseDamageArgs( IConsoleShell shell, EntityUid target, string[] args, [NotNullWhen(true)] out Damage?func) { var entMan = IoCManager.Resolve <IEntityManager>(); if (!int.TryParse(args[1], out var amount)) { shell.WriteLine($"{args[1]} is not a valid damage integer."); func = null; return(false); } if (_prototypeManager.TryIndex <DamageGroupPrototype>(args[0], out var damageGroup)) { func = (entity, ignoreResistances) => { var damage = new DamageSpecifier(damageGroup, amount); EntitySystem.Get <DamageableSystem>().TryChangeDamage(entity, damage, ignoreResistances); shell.WriteLine($"Damaged entity {entMan.GetComponent<MetaDataComponent>(entity).EntityName} with id {entity} for {amount} {damageGroup} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); }; return(true); } // Fall back to DamageType else if (_prototypeManager.TryIndex <DamageTypePrototype>(args[0], out var damageType)) { func = (entity, ignoreResistances) => { var damage = new DamageSpecifier(damageType, amount); EntitySystem.Get <DamageableSystem>().TryChangeDamage(entity, damage, ignoreResistances); shell.WriteLine($"Damaged entity {entMan.GetComponent<MetaDataComponent>(entity).EntityName} with id {entity} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}"); }; return(true); } else { shell.WriteLine($"{args[0]} is not a valid damage class or type."); var types = DamageTypes(); shell.WriteLine(types); func = null; return(false); } }
private void OnDamageTrigger(EntityUid source, EntityUid target, DamageOnTriggerComponent?component = null) { if (!Resolve(source, ref component)) { return; } var damage = new DamageSpecifier(component.Damage); var ev = new BeforeDamageOnTriggerEvent(damage, target); RaiseLocalEvent(source, ev, true); _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances); }
private void OnClickAttack(EntityUid uid, MeleeWeaponComponent comp, ClickAttackEvent args) { args.Handled = true; var curTime = _gameTiming.CurTime; if (curTime < comp.CooldownEnd || !args.Target.IsValid()) { return; } var owner = EntityManager.GetEntity(uid); var target = args.TargetEntity; var location = args.User.Transform.Coordinates; var diff = args.ClickLocation.ToMapPos(owner.EntityManager) - location.ToMapPos(owner.EntityManager); var angle = Angle.FromWorldVec(diff); if (target != null) { // Raise event before doing damage so we can cancel damage if the event is handled var hitEvent = new MeleeHitEvent(new List <IEntity>() { target }, args.User); RaiseLocalEvent(uid, hitEvent, false); if (!hitEvent.Handled) { var targets = new[] { target }; SendAnimation(comp.ClickArc, angle, args.User, owner, targets, comp.ClickAttackEffect, false); RaiseLocalEvent(target.Uid, new AttackedEvent(args.Used, args.User, args.ClickLocation)); _damageableSystem.TryChangeDamage(target.Uid, DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target); } } else { SoundSystem.Play(Filter.Pvs(owner), comp.MissSound.GetSound(), args.User); return; } comp.LastAttackTime = curTime; comp.CooldownEnd = comp.LastAttackTime + TimeSpan.FromSeconds(comp.CooldownTime); RaiseLocalEvent(uid, new RefreshItemCooldownEvent(comp.LastAttackTime, comp.CooldownEnd), false); }
private void OnUserDamageModified(EntityUid uid, BlockingUserComponent component, DamageModifyEvent args) { if (TryComp <BlockingComponent>(component.BlockingItem, out var blockingComponent)) { if (_proto.TryIndex(blockingComponent.PassiveBlockDamageModifer, out DamageModifierSetPrototype? passiveblockModifier) && !blockingComponent.IsBlocking) { args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, passiveblockModifier); } if (_proto.TryIndex(blockingComponent.ActiveBlockDamageModifier, out DamageModifierSetPrototype? activeBlockModifier) && blockingComponent.IsBlocking) { args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, activeBlockModifier); SoundSystem.Play(blockingComponent.BlockSound.GetSound(), Filter.Pvs(component.Owner, entityManager: EntityManager), component.Owner, AudioHelpers.WithVariation(0.2f)); } } }
private void OnDamageChanged(EntityUid uid, BloodstreamComponent component, DamageChangedEvent args) { if (args.DamageDelta is null) { return; } // TODO probably cache this or something. humans get hurt a lot if (!_prototypeManager.TryIndex <DamageModifierSetPrototype>(component.DamageBleedModifiers, out var modifiers)) { return; } var bloodloss = DamageSpecifier.ApplyModifierSet(args.DamageDelta, modifiers); if (bloodloss.Empty) { return; } var oldBleedAmount = component.BleedAmount; var total = bloodloss.Total; var totalFloat = total.Float(); TryModifyBleedAmount(uid, totalFloat, component); var prob = Math.Clamp(totalFloat / 50, 0, 1); var healPopupProb = Math.Clamp(Math.Abs(totalFloat) / 25, 0, 1); if (totalFloat > 0 && _robustRandom.Prob(prob)) { TryModifyBloodLevel(uid, (-total) / 5, component); SoundSystem.Play(Filter.Pvs(uid), component.InstantBloodSound.GetSound(), uid, AudioParams.Default); } else if (totalFloat < 0 && oldBleedAmount > 0 && _robustRandom.Prob(healPopupProb)) { // Magically, this damage has healed some bleeding, likely // because it's burn damage that cauterized their wounds. // We'll play a special sound and popup for feedback. SoundSystem.Play(Filter.Pvs(uid), component.BloodHealedSound.GetSound(), uid, AudioParams.Default); _popupSystem.PopupEntity(Loc.GetString("bloodstream-component-wounds-cauterized"), uid, Filter.Entities(uid)); ; } }
public override void Update(float frameTime) { // Update "in progress" electrocutions RemQueue <ElectrocutionComponent> finishedElectrocutionsQueue = new(); foreach (var(electrocution, consumer) in EntityManager .EntityQuery <ElectrocutionComponent, PowerConsumerComponent>()) { var ftAdjusted = Math.Min(frameTime, electrocution.TimeLeft); electrocution.TimeLeft -= ftAdjusted; electrocution.AccumulatedDamage += consumer.ReceivedPower * ElectrifiedDamagePerWatt * ftAdjusted; if (MathHelper.CloseTo(electrocution.TimeLeft, 0)) { finishedElectrocutionsQueue.Add(electrocution); } } foreach (var finished in finishedElectrocutionsQueue) { var uid = finished.Owner; if (EntityManager.EntityExists(finished.Electrocuting)) { // TODO: damage should be scaled by shock damage multiplier // TODO: better paralyze/jitter timing var damage = new DamageSpecifier( _prototypeManager.Index <DamageTypePrototype>(DamageType), (int)finished.AccumulatedDamage); var actual = _damageableSystem.TryChangeDamage(finished.Electrocuting, damage); if (actual != null) { _logSystem.Add(LogType.Electrocution, $"{ToPrettyString(finished.Owner):entity} received {actual.Total:damage} powered electrocution damage"); } } EntityManager.DeleteEntity(uid); } }
private bool TryParseDamageArgs( IConsoleShell shell, EntityUid target, string[] args, [NotNullWhen(true)] out Damage?func) { if (!float.TryParse(args[1], out var amount)) { shell.WriteLine(Loc.GetString("damage-command-error-quantity", ("arg", args[1]))); func = null; return(false); } if (_prototypeManager.TryIndex <DamageGroupPrototype>(args[0], out var damageGroup)) { func = (entity, ignoreResistances) => { var damage = new DamageSpecifier(damageGroup, amount); EntitySystem.Get <DamageableSystem>().TryChangeDamage(entity, damage, ignoreResistances); }; return(true); } // Fall back to DamageType else if (_prototypeManager.TryIndex <DamageTypePrototype>(args[0], out var damageType)) { func = (entity, ignoreResistances) => { var damage = new DamageSpecifier(damageType, amount); EntitySystem.Get <DamageableSystem>().TryChangeDamage(entity, damage, ignoreResistances); }; return(true); } else { shell.WriteLine(Loc.GetString("damage-command-error-type", ("arg", args[0]))); func = null; return(false); } }
public BeforeDamageOnTriggerEvent(DamageSpecifier damage, EntityUid target) { Damage = damage; Tripper = target; }
public async Task Test() { await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings { NoClient = true, ExtraPrototypes = Prototypes }); var server = pairTracker.Pair.Server; var testMap = await PoolManager.CreateTestMap(pairTracker); var sEntityManager = server.ResolveDependency <IEntityManager>(); var sEntitySystemManager = server.ResolveDependency <IEntitySystemManager>(); EntityUid sDestructibleEntity = default; DamageableComponent sDamageableComponent = null; TestDestructibleListenerSystem sTestThresholdListenerSystem = null; DamageableSystem sDamageableSystem = null; await server.WaitPost(() => { var coordinates = testMap.GridCoords; sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageTypeEntityId, coordinates); sDamageableComponent = IoCManager.Resolve <IEntityManager>().GetComponent <DamageableComponent>(sDestructibleEntity); sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem <TestDestructibleListenerSystem>(); sTestThresholdListenerSystem.ThresholdsReached.Clear(); sDamageableSystem = sEntitySystemManager.GetEntitySystem <DamageableSystem>(); }); await server.WaitRunTicks(5); await server.WaitAssertion(() => { Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); }); await server.WaitAssertion(() => { var bluntDamageType = IoCManager.Resolve <IPrototypeManager>().Index <DamageTypePrototype>("TestBlunt"); var slashDamageType = IoCManager.Resolve <IPrototypeManager>().Index <DamageTypePrototype>("TestSlash"); var bluntDamage = new DamageSpecifier(bluntDamageType, 5); var slashDamage = new DamageSpecifier(slashDamageType, 5); // Raise blunt damage to 5 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // No thresholds reached yet, the earliest one is at 10 damage Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise blunt damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // No threshold reached, slash needs to be 10 as well Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise slash damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * 2, true); // One threshold reached, blunt 10 + slash 10 Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); // Threshold blunt 10 + slash 10 var msg = sTestThresholdListenerSystem.ThresholdsReached[0]; var threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Is.Empty); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); Assert.IsInstanceOf <AndTrigger>(threshold.Trigger); var trigger = (AndTrigger)threshold.Trigger; Assert.IsInstanceOf <DamageTypeTrigger>(trigger.Triggers[0]); Assert.IsInstanceOf <DamageTypeTrigger>(trigger.Triggers[1]); sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Raise blunt damage to 20 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 2, true); // No new thresholds reached Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise slash damage to 20 sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * 2, true); // No new thresholds reached Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Lower blunt damage to 0 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * -4, true); // No new thresholds reached, healing should not trigger it Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise blunt damage back up to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 2, true); // 10 blunt + 10 slash threshold reached, blunt was healed and brought back to its threshold amount and slash stayed the same Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Heal both types of damage to 0 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * -2, true); sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * -4, true); // No new thresholds reached, healing should not trigger it Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise blunt damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 2, true); // No new thresholds reached Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise slash damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * 2, true); // Both types of damage were healed and then raised again, the threshold should have been reached as triggers once is default false Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); // Threshold blunt 10 + slash 10 msg = sTestThresholdListenerSystem.ThresholdsReached[0]; threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Is.Empty); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); Assert.IsInstanceOf <AndTrigger>(threshold.Trigger); trigger = (AndTrigger)threshold.Trigger; Assert.IsInstanceOf <DamageTypeTrigger>(trigger.Triggers[0]); Assert.IsInstanceOf <DamageTypeTrigger>(trigger.Triggers[1]); sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Change triggers once to true threshold.TriggersOnce = true; // Heal blunt and slash back to 0 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * -2, true); sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * -2, true); // No new thresholds reached from healing Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise blunt damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 2, true); // No new thresholds reached Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Raise slash damage to 10 sDamageableSystem.TryChangeDamage(sDestructibleEntity, slashDamage * 2, true); // No new thresholds reached as triggers once is set to true and it already triggered before Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); }); await pairTracker.CleanReturnAsync(); }
private void OnDamageModify(EntityUid uid, ArmorComponent component, DamageModifyEvent args) { args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, component.Modifiers); }
private void OnWideAttack(EntityUid uid, MeleeWeaponComponent comp, WideAttackEvent args) { args.Handled = true; var curTime = _gameTiming.CurTime; if (curTime < comp.CooldownEnd) { return; } var owner = EntityManager.GetEntity(uid); var location = args.User.Transform.Coordinates; var diff = args.ClickLocation.ToMapPos(owner.EntityManager) - location.ToMapPos(owner.EntityManager); var angle = Angle.FromWorldVec(diff); // This should really be improved. GetEntitiesInArc uses pos instead of bounding boxes. var entities = ArcRayCast(args.User.Transform.WorldPosition, angle, comp.ArcWidth, comp.Range, owner.Transform.MapID, args.User); var hitEntities = new List <IEntity>(); foreach (var entity in entities) { if (entity.IsInContainer() || entity == args.User) { continue; } if (EntityManager.HasComponent <DamageableComponent>(entity.Uid)) { hitEntities.Add(entity); } } // Raise event before doing damage so we can cancel damage if handled var hitEvent = new MeleeHitEvent(hitEntities, args.User); RaiseLocalEvent(uid, hitEvent, false); SendAnimation(comp.Arc, angle, args.User, owner, hitEntities); if (!hitEvent.Handled) { if (entities.Count != 0) { SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), entities.First().Transform.Coordinates); } else { SoundSystem.Play(Filter.Pvs(owner), comp.MissSound.GetSound(), args.User.Transform.Coordinates); } foreach (var entity in hitEntities) { RaiseLocalEvent(entity.Uid, new AttackedEvent(args.Used, args.User, args.ClickLocation)); _damageableSystem.TryChangeDamage(entity.Uid, DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); } } comp.LastAttackTime = curTime; comp.CooldownEnd = comp.LastAttackTime + TimeSpan.FromSeconds(comp.ArcCooldownTime); RaiseLocalEvent(uid, new RefreshItemCooldownEvent(comp.LastAttackTime, comp.CooldownEnd), false); }
public async Task Test() { var server = StartServer(new ServerContentIntegrationOption { ExtraPrototypes = Prototypes }); await server.WaitIdleAsync(); var sEntityManager = server.ResolveDependency <IEntityManager>(); var sMapManager = server.ResolveDependency <IMapManager>(); var sPrototypeManager = server.ResolveDependency <IPrototypeManager>(); var sEntitySystemManager = server.ResolveDependency <IEntitySystemManager>(); EntityUid sDestructibleEntity = default; DamageableComponent sDamageableComponent = null; DestructibleComponent sDestructibleComponent = null; TestDestructibleListenerSystem sTestThresholdListenerSystem = null; DamageableSystem sDamageableSystem = null; await server.WaitPost(() => { var gridId = GetMainGrid(sMapManager).GridEntityId; var coordinates = new EntityCoordinates(gridId, 0, 0); sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleEntityId, coordinates); sDamageableComponent = IoCManager.Resolve <IEntityManager>().GetComponent <DamageableComponent>(sDestructibleEntity); sDestructibleComponent = IoCManager.Resolve <IEntityManager>().GetComponent <DestructibleComponent>(sDestructibleEntity); sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem <TestDestructibleListenerSystem>(); sTestThresholdListenerSystem.ThresholdsReached.Clear(); sDamageableSystem = sEntitySystemManager.GetEntitySystem <DamageableSystem>(); }); await server.WaitRunTicks(5); await server.WaitAssertion(() => { Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); }); await server.WaitAssertion(() => { var bluntDamage = new DamageSpecifier(sPrototypeManager.Index <DamageTypePrototype>("TestBlunt"), 10); sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // No thresholds reached yet, the earliest one is at 20 damage Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // Only one threshold reached, 20 Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); // Threshold 20 var msg = sTestThresholdListenerSystem.ThresholdsReached[0]; var threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Is.Empty); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); sTestThresholdListenerSystem.ThresholdsReached.Clear(); sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 3, true); // One threshold reached, 50, since 20 already triggered before and it has not been healed below that amount Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); // Threshold 50 msg = sTestThresholdListenerSystem.ThresholdsReached[0]; threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); var soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; var spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; var actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(soundThreshold.Sound.GetSound(), Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Damage for 50 again, up to 100 now sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 5, true); // No thresholds reached as they weren't healed below the trigger amount Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached); // Set damage to 0 sDamageableSystem.SetAllDamage(sDamageableComponent, 0); // Damage for 100, up to 100 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 10, true); // Two thresholds reached as damage increased past the previous, 20 and 50 Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(2)); sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Heal the entity for 40 damage, down to 60 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * -4, true); // Thresholds don't work backwards Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); // Damage for 10, up to 70 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // Not enough healing to de-trigger a threshold Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); // Heal by 30, down to 40 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * -3, true); // Thresholds don't work backwards Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); // Damage up to 50 again sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage, true); // The 50 threshold should have triggered again, after being healed Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1)); msg = sTestThresholdListenerSystem.ThresholdsReached[0]; threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; // Check that it matches the YAML prototype Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(soundThreshold.Sound.GetSound(), Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); // Reset thresholds reached sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Heal all damage sDamageableSystem.SetAllDamage(sDamageableComponent, 0); // Damage up to 50 sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 5, true); // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(FixedPoint2.New(50))); // Both thresholds should have triggered Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Has.Exactly(2).Items); // Verify the first one, should be the lowest one (20) msg = sTestThresholdListenerSystem.ThresholdsReached[0]; var trigger = (DamageTrigger)msg.Threshold.Trigger; Assert.NotNull(trigger); Assert.That(trigger.Damage, Is.EqualTo(20)); threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Behaviors, Is.Empty); // Verify the second one, should be the highest one (50) msg = sTestThresholdListenerSystem.ThresholdsReached[1]; trigger = (DamageTrigger)msg.Threshold.Trigger; Assert.NotNull(trigger); Assert.That(trigger.Damage, Is.EqualTo(50)); threshold = msg.Threshold; Assert.That(threshold.Behaviors, Has.Count.EqualTo(3)); soundThreshold = (PlaySoundBehavior)threshold.Behaviors[0]; spawnThreshold = (SpawnEntitiesBehavior)threshold.Behaviors[1]; actsThreshold = (DoActsBehavior)threshold.Behaviors[2]; // Check that it matches the YAML prototype Assert.That(actsThreshold.Acts, Is.EqualTo(ThresholdActs.Breakage)); Assert.That(soundThreshold.Sound.GetSound(), Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(spawnThreshold.Spawn, Is.Not.Null); Assert.That(spawnThreshold.Spawn.Count, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId)); Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.NotNull(threshold.Trigger); Assert.That(threshold.Triggered, Is.True); // Reset thresholds reached sTestThresholdListenerSystem.ThresholdsReached.Clear(); // Heal the entity completely sDamageableSystem.SetAllDamage(sDamageableComponent, 0); // Check that the entity has 0 damage Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(FixedPoint2.Zero)); // Set both thresholds to only trigger once foreach (var destructibleThreshold in sDestructibleComponent.Thresholds) { Assert.NotNull(destructibleThreshold.Trigger); destructibleThreshold.TriggersOnce = true; } // Damage the entity up to 50 damage again sDamageableSystem.TryChangeDamage(sDestructibleEntity, bluntDamage * 5, true); // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(FixedPoint2.New(50))); // No thresholds should have triggered as they were already triggered before, and they are set to only trigger once Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); // Set both thresholds to trigger multiple times foreach (var destructibleThreshold in sDestructibleComponent.Thresholds) { Assert.NotNull(destructibleThreshold.Trigger); destructibleThreshold.TriggersOnce = false; } // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(FixedPoint2.New(50))); // They shouldn't have been triggered by changing TriggersOnce Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty); }); }