Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -39,8 +39,10 @@ namespace Content.Shared.Alert
|
||||
VeryVeryGood,
|
||||
MoodDead,
|
||||
Arousal, //AMOUR
|
||||
//WD end
|
||||
CultBuffed,
|
||||
Knockdown,
|
||||
RecentlyBlocked,
|
||||
//WD end
|
||||
PilotingShuttle,
|
||||
Peckish,
|
||||
Starving,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -19,8 +20,33 @@ public sealed partial class BlockingSystem
|
||||
SubscribeLocalEvent<BlockingUserComponent, ContainerGettingInsertedAttemptEvent>(OnInsertAttempt);
|
||||
SubscribeLocalEvent<BlockingUserComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<BlockingUserComponent, EntityTerminatingEvent>(OnEntityTerminating);
|
||||
|
||||
SubscribeLocalEvent<BlockingUserComponent, MeleeBlockAttemptEvent>(OnMeleeBlockAttempt); // WD
|
||||
}
|
||||
|
||||
// WD START
|
||||
private void OnMeleeBlockAttempt(Entity<BlockingUserComponent> ent, ref MeleeBlockAttemptEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var uid = ent.Comp.BlockingItem;
|
||||
if (!TryComp(uid, out BlockingComponent? blocking) || !blocking.IsBlocking)
|
||||
return;
|
||||
|
||||
if (TryComp(uid.Value, out ItemToggleComponent? toggle) && !toggle.Activated)
|
||||
return;
|
||||
|
||||
if (!TryComp(uid.Value, out DamageableComponent? damageable))
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(blocking.BlockSound, ent, args.Attacker);
|
||||
_popupSystem.PopupPredicted(Loc.GetString("melee-block-event-blocked"), ent, args.Attacker);
|
||||
_damageable.TryChangeDamage(uid.Value, args.Damage, damageable: damageable);
|
||||
args.Handled = true;
|
||||
}
|
||||
// WD END
|
||||
|
||||
private void OnParentChanged(EntityUid uid, BlockingUserComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
UserStopBlocking(uid, component);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Standing.Systems;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
@@ -30,6 +31,7 @@ public abstract class SharedTentacleGun : EntitySystem
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly SharedStandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -111,7 +113,7 @@ public abstract class SharedTentacleGun : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.Embedded))
|
||||
if (!TryComp<StandingStateComponent>(args.Embedded, out var standing) || !standing.CanLieDown)
|
||||
{
|
||||
DeleteProjectile(uid);
|
||||
return;
|
||||
@@ -156,6 +158,7 @@ public abstract class SharedTentacleGun : EntitySystem
|
||||
private bool PullMob(ProjectileEmbedEvent args)
|
||||
{
|
||||
_standing.TryLieDown(args.Embedded);
|
||||
_stun.TryKnockdown(args.Embedded, TimeSpan.FromSeconds(4), true);
|
||||
|
||||
_throwingSystem.TryThrow(args.Embedded, Transform(args.Shooter!.Value).Coordinates, 5f);
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ public sealed partial class StaminaSystem : EntitySystem
|
||||
|
||||
private void OnMeleeHit(EntityUid uid, StaminaDamageOnHitComponent component, MeleeHitEvent args)
|
||||
{
|
||||
if (args.Handled) // WD
|
||||
return;
|
||||
|
||||
if (!args.IsHit ||
|
||||
!args.HitEntities.Any() ||
|
||||
component.Damage <= 0f)
|
||||
|
||||
@@ -10,5 +10,6 @@ public sealed partial class FlashOnTriggerComponent : Component
|
||||
[DataField] public float Range = 1.0f;
|
||||
[DataField] public float Duration = 8.0f;
|
||||
[DataField] public float Probability = 1.0f;
|
||||
[DataField] public bool ForceStun; // WD
|
||||
[DataField] public float StunTime; // WD
|
||||
[DataField] public float KnockdownTime; // WD
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Wieldable;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Item.ItemToggle;
|
||||
/// <summary>
|
||||
@@ -22,6 +23,7 @@ public abstract class SharedItemToggleSystem : EntitySystem
|
||||
[Dependency] private readonly SharedPointLightSystem _light = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -240,6 +242,9 @@ public abstract class SharedItemToggleSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted) // WD
|
||||
return;
|
||||
|
||||
if (args.Activated)
|
||||
{
|
||||
if (activeSound.ActiveSound != null && activeSound.PlayingStream == null)
|
||||
|
||||
@@ -9,11 +9,11 @@ public sealed partial class JitteringComponent : Component
|
||||
{
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Amplitude { get; set; }
|
||||
public float Amplitude { get; set; } = 10f;
|
||||
|
||||
[AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Frequency { get; set; }
|
||||
public float Frequency { get; set; } = 4f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 LastJitter { get; set; }
|
||||
|
||||
@@ -11,6 +11,7 @@ using Content.Shared._White.Wizard.Timestop;
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -52,6 +53,7 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<StandingStateComponent, StandingUpDoAfterEvent>(OnStandingUpDoAfter);
|
||||
SubscribeLocalEvent<StandingStateComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeed);
|
||||
SubscribeLocalEvent<StandingStateComponent, TileFrictionEvent>(OnTileFriction);
|
||||
SubscribeLocalEvent<StandingStateComponent, SlipAttemptEvent>(OnSlipAttempt);
|
||||
|
||||
InitializeColliding();
|
||||
@@ -76,7 +78,7 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
|
||||
RaiseNetworkEvent(new CheckAutoGetUpEvent()); // WD EDIT
|
||||
|
||||
if (_stun.IsParalyzed(uid))
|
||||
if (HasComp<KnockedDownComponent>(uid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -98,12 +100,9 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
|
||||
private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || _stun.IsParalyzed(uid)) // WD EDIT
|
||||
{
|
||||
if (args.Handled || args.Cancelled || HasComp<KnockedDownComponent>(uid) ||
|
||||
_mobState.IsIncapacitated(uid) || !Stand(uid)) // WD EDIT
|
||||
component.CurrentState = StandingState.Lying;
|
||||
return;
|
||||
}
|
||||
Stand(uid);
|
||||
}
|
||||
|
||||
private void OnRefreshMovementSpeed(EntityUid uid, StandingStateComponent component,
|
||||
@@ -115,6 +114,12 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
args.ModifySpeed(1f, 1f);
|
||||
}
|
||||
|
||||
private void OnTileFriction(Entity<StandingStateComponent> ent, ref TileFrictionEvent args)
|
||||
{
|
||||
if (IsDown(ent))
|
||||
args.Modifier *= SharedStunSystem.KnockDownModifier;
|
||||
}
|
||||
|
||||
private void OnSlipAttempt(EntityUid uid, StandingStateComponent component, SlipAttemptEvent args)
|
||||
{
|
||||
if (IsDown(uid))
|
||||
@@ -217,6 +222,9 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) // WD EDIT
|
||||
return false;
|
||||
|
||||
if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp)
|
||||
return true;
|
||||
|
||||
// This is just to avoid most callers doing this manually saving boilerplate
|
||||
// 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to.
|
||||
// We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway
|
||||
@@ -226,9 +234,6 @@ public abstract partial class SharedStandingStateSystem : EntitySystem
|
||||
RaiseLocalEvent(uid, new DropHandItemsEvent());
|
||||
}
|
||||
|
||||
if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp)
|
||||
return true;
|
||||
|
||||
var msg = new DownAttemptEvent();
|
||||
RaiseLocalEvent(uid, msg);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class SharedStunSystem : EntitySystem
|
||||
SubscribeLocalEvent<KnockedDownComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<SlowedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovespeed);
|
||||
|
||||
SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
|
||||
//SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
|
||||
|
||||
// Attempt event subscriptions.
|
||||
SubscribeLocalEvent<StunnedComponent, ChangeDirectionAttemptEvent>(OnAttempt);
|
||||
@@ -260,17 +260,10 @@ public abstract class SharedStunSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnKnockedTileFriction(EntityUid uid, KnockedDownComponent component, ref TileFrictionEvent args)
|
||||
/*private void OnKnockedTileFriction(EntityUid uid, KnockedDownComponent component, ref TileFrictionEvent args)
|
||||
{
|
||||
args.Modifier *= KnockDownModifier;
|
||||
}
|
||||
|
||||
//WD EDIT START
|
||||
public bool IsParalyzed(EntityUid uid)
|
||||
{
|
||||
return HasComp<StunnedComponent>(uid) || HasComp<KnockedDownComponent>(uid);
|
||||
}
|
||||
//WD EDIT END
|
||||
}*/
|
||||
|
||||
#region Attempt Event Handling
|
||||
|
||||
|
||||
@@ -97,6 +97,10 @@ public record struct GetMeleeAttackRateEvent(EntityUid Weapon, float Rate, float
|
||||
public record struct GetHeavyDamageModifierEvent(EntityUid Weapon, FixedPoint2 DamageModifier, float Multipliers, EntityUid User);
|
||||
|
||||
// WD START
|
||||
[ByRefEvent]
|
||||
public record struct MeleeBlockAttemptEvent(EntityUid Attacker, bool Blocked = false);
|
||||
public sealed class MeleeBlockAttemptEvent(EntityUid attacker, DamageSpecifier damage) : HandledEntityEventArgs
|
||||
{
|
||||
public EntityUid Attacker = attacker;
|
||||
|
||||
public DamageSpecifier Damage = damage;
|
||||
}
|
||||
// WD END
|
||||
|
||||
@@ -64,6 +64,9 @@ public sealed class MeleeThrowOnHitSystem : EntitySystem
|
||||
|
||||
private void OnMeleeHit(Entity<MeleeThrowOnHitComponent> ent, ref MeleeHitEvent args)
|
||||
{
|
||||
if (args.Handled) // WD
|
||||
return;
|
||||
|
||||
var (_, comp) = ent;
|
||||
if (!args.IsHit)
|
||||
return;
|
||||
|
||||
@@ -70,7 +70,10 @@ public sealed partial class MeleeWeaponComponent : Component
|
||||
public bool CanAttackSelf = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public bool CanMiss = true;
|
||||
public bool CanBeBlocked = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public bool CanMiss;
|
||||
|
||||
[DataField]
|
||||
public EntityWhitelist? AttackWhitelist;
|
||||
@@ -78,6 +81,14 @@ public sealed partial class MeleeWeaponComponent : Component
|
||||
[DataField]
|
||||
public EntityWhitelist? AttackBlacklist;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextMobAttack;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public float? EquipCooldown;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public bool IgnoreResistances;
|
||||
|
||||
@@ -114,12 +125,11 @@ public sealed partial class MeleeWeaponComponent : Component
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
||||
public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1);
|
||||
|
||||
// TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2
|
||||
/// <summary>
|
||||
/// Nearest edge range to hit an entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
|
||||
public float Range = 1.5f;
|
||||
public float Range = 1.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Total width of the angle for wide attacks.
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared._White;
|
||||
using Content.Shared._White.Implants.NeuroControl;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -94,14 +95,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
private void OnMapInit(EntityUid uid, MeleeWeaponComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.NextAttack > Timing.CurTime)
|
||||
if (component.NextAttack > Timing.CurTime || component.NextMobAttack > Timing.CurTime) // WD EDIT
|
||||
Log.Warning($"Initializing a map that contains an entity that is on cooldown. Entity: {ToPrettyString(uid)}");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnMeleeShotAttempted(EntityUid uid, MeleeWeaponComponent comp, ref ShotAttemptedEvent args)
|
||||
{
|
||||
if (comp.NextAttack > Timing.CurTime)
|
||||
if (comp.NextAttack > Timing.CurTime || comp.NextMobAttack > Timing.CurTime) // WD EDIT
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
@@ -110,16 +111,28 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
if (!TryComp<GunComponent>(uid, out var gun))
|
||||
return;
|
||||
|
||||
// WD EDIT START
|
||||
var dirty = false;
|
||||
if (gun.NextFire > component.NextAttack)
|
||||
{
|
||||
component.NextAttack = gun.NextFire;
|
||||
Dirty(uid, component);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (gun.NextFire > component.NextMobAttack)
|
||||
{
|
||||
component.NextMobAttack = gun.NextFire;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, component);
|
||||
// WD EDIT END
|
||||
}
|
||||
|
||||
private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args)
|
||||
{
|
||||
var attackRate = GetAttackRate(uid, args.User, component);
|
||||
var attackRate = component.EquipCooldown ?? GetAttackRate(uid, args.User, component); // WD EDIT
|
||||
if (attackRate.Equals(0f))
|
||||
return;
|
||||
|
||||
@@ -133,11 +146,21 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
var curTime = Timing.CurTime;
|
||||
var minimum = curTime + TimeSpan.FromSeconds(1 / attackRate);
|
||||
|
||||
if (minimum < component.NextAttack)
|
||||
return;
|
||||
|
||||
component.NextAttack = minimum;
|
||||
Dirty(uid, component);
|
||||
// WD EDIT START
|
||||
var dirty = false;
|
||||
if (minimum > component.NextAttack)
|
||||
{
|
||||
component.NextAttack = minimum;
|
||||
dirty = true;
|
||||
}
|
||||
if (minimum > component.NextMobAttack)
|
||||
{
|
||||
component.NextMobAttack = minimum;
|
||||
dirty = true;
|
||||
}
|
||||
if (dirty)
|
||||
Dirty(uid, component);
|
||||
// WD EDIT END
|
||||
}
|
||||
|
||||
private void OnGetBonusMeleeDamage(EntityUid uid, BonusMeleeDamageComponent component, ref GetMeleeDamageEvent args)
|
||||
@@ -339,6 +362,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
return AttemptAttack(user, weaponUid, weapon, new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(targetXform.Coordinates)), null);
|
||||
}
|
||||
|
||||
private enum UpdateNextAttack : byte // WD
|
||||
{
|
||||
Mob,
|
||||
NonMob,
|
||||
Both
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a windup is finished and an attack is tried.
|
||||
/// </summary>
|
||||
@@ -347,16 +377,16 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
var curTime = Timing.CurTime;
|
||||
|
||||
if (weapon.NextAttack > curTime)
|
||||
return false;
|
||||
|
||||
if (!CombatMode.IsInCombatMode(user))
|
||||
return false;
|
||||
|
||||
var update = UpdateNextAttack.Both; // WD
|
||||
switch (attack)
|
||||
{
|
||||
case LightAttackEvent light:
|
||||
var lightTarget = GetEntity(light.Target);
|
||||
update = lightTarget == null ? UpdateNextAttack.Both :
|
||||
IsMob(lightTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD
|
||||
|
||||
if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon)))
|
||||
return false;
|
||||
@@ -392,6 +422,8 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
break;
|
||||
case DisarmAttackEvent disarm:
|
||||
var disarmTarget = GetEntity(disarm.Target);
|
||||
update = disarmTarget == null ? UpdateNextAttack.Both :
|
||||
IsMob(disarmTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD
|
||||
|
||||
if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true))
|
||||
return false;
|
||||
@@ -405,23 +437,52 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
// Windup time checked elsewhere.
|
||||
var fireRate = TimeSpan.FromSeconds(1f / GetAttackRate(weaponUid, user, weapon));
|
||||
|
||||
if (attack is LightAttackEvent _)
|
||||
{
|
||||
fireRate *= 0.8f;
|
||||
}
|
||||
|
||||
var swings = 0;
|
||||
|
||||
// TODO: If we get autoattacks then probably need a shotcounter like guns so we can do timing properly.
|
||||
if (weapon.NextAttack < curTime)
|
||||
weapon.NextAttack = curTime;
|
||||
|
||||
while (weapon.NextAttack <= curTime)
|
||||
// WD EDIT START
|
||||
switch(update)
|
||||
{
|
||||
weapon.NextAttack += fireRate;
|
||||
swings++;
|
||||
case UpdateNextAttack.Mob:
|
||||
if (weapon.NextMobAttack > curTime)
|
||||
return false;
|
||||
break;
|
||||
case UpdateNextAttack.NonMob:
|
||||
if (weapon.NextAttack > curTime)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
if (weapon.NextAttack > curTime || weapon.NextMobAttack > curTime)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (update != UpdateNextAttack.Mob)
|
||||
{
|
||||
// TODO: If we get autoattacks then probably need a shotcounter like guns so we can do timing properly.
|
||||
if (weapon.NextAttack < curTime)
|
||||
weapon.NextAttack = curTime;
|
||||
|
||||
while (weapon.NextAttack <= curTime)
|
||||
{
|
||||
weapon.NextAttack += fireRate;
|
||||
swings++;
|
||||
}
|
||||
}
|
||||
|
||||
if (update != UpdateNextAttack.NonMob)
|
||||
{
|
||||
if (weapon.NextMobAttack < curTime)
|
||||
weapon.NextMobAttack = curTime;
|
||||
|
||||
while (weapon.NextMobAttack <= curTime)
|
||||
{
|
||||
weapon.NextMobAttack += fireRate;
|
||||
if (update == UpdateNextAttack.Mob)
|
||||
swings++;
|
||||
}
|
||||
}
|
||||
// WD EDIT END
|
||||
|
||||
Dirty(weaponUid, weapon);
|
||||
|
||||
// Do this AFTER attack so it doesn't spam every tick
|
||||
@@ -452,7 +513,10 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
? weapon.MissAnimation
|
||||
: weapon.Animation;
|
||||
if (miss)
|
||||
{
|
||||
weapon.NextAttack -= fireRate / 2f;
|
||||
weapon.NextMobAttack -= fireRate / 2f;
|
||||
}
|
||||
// WD EDIT END
|
||||
break;
|
||||
case DisarmAttackEvent disarm:
|
||||
@@ -520,13 +584,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
|
||||
|
||||
// WD START
|
||||
var blockEvent = new MeleeBlockAttemptEvent(user);
|
||||
RaiseLocalEvent(target.Value, ref blockEvent);
|
||||
if (blockEvent.Blocked)
|
||||
return;
|
||||
// WD END
|
||||
|
||||
// Raise event before doing damage so we can cancel damage if the event is handled
|
||||
var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage, null);
|
||||
RaiseLocalEvent(meleeUid, hitEvent);
|
||||
@@ -663,19 +720,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
|
||||
|
||||
// WD START
|
||||
foreach (var target in new List<EntityUid>(targets))
|
||||
{
|
||||
var blockEvent = new MeleeBlockAttemptEvent(user);
|
||||
RaiseLocalEvent(target, ref blockEvent);
|
||||
if (blockEvent.Blocked)
|
||||
targets.Remove(target);
|
||||
}
|
||||
|
||||
if (targets.Count == 0)
|
||||
return true;
|
||||
// WD END
|
||||
|
||||
// Raise event before doing damage so we can cancel damage if the event is handled
|
||||
var hitEvent = new MeleeHitEvent(targets, user, meleeUid, damage, direction);
|
||||
RaiseLocalEvent(meleeUid, hitEvent);
|
||||
@@ -899,4 +943,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
Dirty(uid, meleeWeapon);
|
||||
}
|
||||
|
||||
protected bool IsMob(EntityUid uid)
|
||||
{
|
||||
return HasComp<InputMoverComponent>(uid) || HasComp<MobMoverComponent>(uid) || HasComp<CombatModeComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +120,23 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
if (!TryComp<MeleeWeaponComponent>(uid, out var melee))
|
||||
return;
|
||||
|
||||
// WD EDIT START
|
||||
var dirty = false;
|
||||
if (melee.NextAttack > component.NextFire)
|
||||
{
|
||||
component.NextFire = melee.NextAttack;
|
||||
Dirty(uid, component);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (melee.NextMobAttack > component.NextFire)
|
||||
{
|
||||
component.NextFire = melee.NextMobAttack;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, component);
|
||||
// WD EDIT END
|
||||
}
|
||||
|
||||
private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args)
|
||||
|
||||
8
Content.Shared/_White/Blocking/BlockBlockerComponent.cs
Normal file
8
Content.Shared/_White/Blocking/BlockBlockerComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._White.Blocking;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BlockBlockerComponent : Component
|
||||
{
|
||||
}
|
||||
16
Content.Shared/_White/Blocking/MeleeBlockComponent.cs
Normal file
16
Content.Shared/_White/Blocking/MeleeBlockComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Shared._White.Blocking;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MeleeBlockComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(3.1);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier BlockSound = new SoundPathSpecifier("/Audio/Weapons/block_metal1.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.25f)
|
||||
};
|
||||
}
|
||||
97
Content.Shared/_White/Blocking/MeleeBlockSystem.cs
Normal file
97
Content.Shared/_White/Blocking/MeleeBlockSystem.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Content.Shared._White.BetrayalDagger;
|
||||
using Content.Shared.Blocking;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._White.Blocking;
|
||||
|
||||
public sealed class MeleeBlockSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffect = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, MeleeBlockAttemptEvent>(OnBlockAttempt,
|
||||
after: new[] {typeof(BlockingSystem)});
|
||||
SubscribeLocalEvent<MeleeWeaponComponent, MeleeHitEvent>(OnHit,
|
||||
before: new[] {typeof(StaminaSystem), typeof(MeleeThrowOnHitSystem)},
|
||||
after: new[] {typeof(BackstabSystem)});
|
||||
SubscribeLocalEvent<MeleeBlockComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<MeleeBlockComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("melee-block-component-delay", ("delay", ent.Comp.Delay.TotalSeconds)));
|
||||
}
|
||||
|
||||
private void OnHit(Entity<MeleeWeaponComponent> ent, ref MeleeHitEvent args)
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.CanBeBlocked || !args.IsHit || args.Handled)
|
||||
return;
|
||||
|
||||
if (args.Direction != null || args.HitEntities.Count != 1) // Heavy attacks are unblockable
|
||||
return;
|
||||
|
||||
var hitEntity = args.HitEntities[0];
|
||||
|
||||
if (hitEntity == args.User)
|
||||
return;
|
||||
|
||||
var ev = new MeleeBlockAttemptEvent(args.User, args.BaseDamage + args.BonusDamage);
|
||||
RaiseLocalEvent(hitEntity, ev);
|
||||
if (ev.Handled)
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBlockAttempt(Entity<HandsComponent> ent, ref MeleeBlockAttemptEvent args)
|
||||
{
|
||||
if (args.Handled || HasComp<BlockBlockerComponent>(ent))
|
||||
return;
|
||||
|
||||
if (!TryComp(ent, out StatusEffectsComponent? statusEffects))
|
||||
return;
|
||||
|
||||
var uid = ent.Comp.ActiveHandEntity;
|
||||
if (!TryComp(uid, out MeleeBlockComponent? block))
|
||||
return;
|
||||
|
||||
if (TryComp(uid.Value, out ItemToggleComponent? toggle) && !toggle.Activated)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(block.BlockSound, ent, args.Attacker);
|
||||
_popupSystem.PopupPredicted(Loc.GetString("melee-block-event-blocked"), ent, args.Attacker);
|
||||
_damageable.TryChangeDamage(uid.Value, args.Damage);
|
||||
TryBlockBlocking(ent, block.Delay, true, statusEffects);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public bool TryBlockBlocking(EntityUid uid, TimeSpan time, bool refresh,
|
||||
StatusEffectsComponent? status = null)
|
||||
{
|
||||
if (time <= TimeSpan.Zero)
|
||||
return false;
|
||||
|
||||
if (!Resolve(uid, ref status, false))
|
||||
return false;
|
||||
|
||||
return _statusEffect.TryAddStatusEffect<BlockBlockerComponent>(uid, "RecentlyBlocked", time, refresh);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public sealed class FlashSoundSuppressionSystem : EntitySystem
|
||||
args.Args.MaxRange = MathF.Min(args.Args.MaxRange, ent.Comp.MaxRange);
|
||||
}
|
||||
|
||||
public void Stun(EntityUid target, float duration, float distance, float range)
|
||||
public void Stun(EntityUid target, float stunDuration, float knockdownDuration, float distance, float range)
|
||||
{
|
||||
if (TryComp<FlashSoundSuppressionComponent>(target, out var suppression))
|
||||
range = MathF.Min(range, suppression.MaxRange);
|
||||
@@ -38,11 +38,13 @@ public sealed class FlashSoundSuppressionSystem : EntitySystem
|
||||
if (distance > range)
|
||||
return;
|
||||
|
||||
var stunTime = float.Lerp(duration, 0f, distance / range);
|
||||
if (stunTime <= 0f)
|
||||
return;
|
||||
var knockdownTime = float.Lerp(knockdownDuration, 0f, distance / range);
|
||||
if (knockdownTime > 0f)
|
||||
_stunSystem.TryKnockdown(target, TimeSpan.FromSeconds(knockdownTime), true);
|
||||
|
||||
_stunSystem.TryParalyze(target, TimeSpan.FromSeconds(stunTime / 1000f), true);
|
||||
var stunTime = float.Lerp(stunDuration, 0f, distance / range);
|
||||
if (stunTime > 0f)
|
||||
_stunSystem.TryStun(target, TimeSpan.FromSeconds(stunTime), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._White.Item.DelayedKnockdown;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class DelayedKnockdownComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan KnockdownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan KnockdownMoment = TimeSpan.MaxValue;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Content.Shared._White.Item.DelayedKnockdown;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DelayedKnockdownOnHitComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(2);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan KnockdownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan StutterTime = TimeSpan.FromSeconds(16);
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan JitterTime = TimeSpan.FromSeconds(40);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Speech.EntitySystems;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._White.Item.DelayedKnockdown;
|
||||
|
||||
public sealed class DelayedKnockdownOnHitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedJitteringSystem _jitter = default!;
|
||||
[Dependency] private readonly SharedStutteringSystem _stutter = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DelayedKnockdownOnHitComponent, StaminaMeleeHitEvent>(OnHit);
|
||||
}
|
||||
|
||||
private void OnHit(Entity<DelayedKnockdownOnHitComponent> ent, ref StaminaMeleeHitEvent args)
|
||||
{
|
||||
var jitterTime = ent.Comp.JitterTime;
|
||||
var stutterTime = ent.Comp.StutterTime;
|
||||
foreach (var (uid, _) in args.HitList)
|
||||
{
|
||||
if (!TryComp(uid, out StatusEffectsComponent? statusEffects))
|
||||
continue;
|
||||
|
||||
if (!TryComp(uid, out StandingStateComponent? standingState) || !standingState.CanLieDown)
|
||||
continue;
|
||||
|
||||
if (jitterTime > TimeSpan.Zero)
|
||||
_jitter.DoJitter(uid, jitterTime, true, status: statusEffects);
|
||||
if (stutterTime > TimeSpan.Zero)
|
||||
_stutter.DoStutter(uid, stutterTime, true, statusEffects);
|
||||
if (HasComp<KnockedDownComponent>(uid))
|
||||
continue;
|
||||
var delayedKnockdown = EnsureComp<DelayedKnockdownComponent>(uid);
|
||||
delayedKnockdown.KnockdownTime = TimeSpan.FromSeconds(Math.Max(ent.Comp.KnockdownTime.TotalSeconds,
|
||||
delayedKnockdown.KnockdownTime.TotalSeconds));
|
||||
var knockdownMoment = _timing.CurTime + ent.Comp.Delay;
|
||||
if (knockdownMoment < delayedKnockdown.KnockdownMoment)
|
||||
delayedKnockdown.KnockdownMoment = knockdownMoment;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<DelayedKnockdownComponent>();
|
||||
while (query.MoveNext(out var uid, out var delayedKnockdown))
|
||||
{
|
||||
if (delayedKnockdown.KnockdownMoment > _timing.CurTime)
|
||||
continue;
|
||||
|
||||
_stun.TryKnockdown(uid, delayedKnockdown.KnockdownTime, true);
|
||||
RemCompDeferred<DelayedKnockdownComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Content.Shared._White.Item.Telebaton;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class TelebatonComponent : Component
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan KnockdownTime = TimeSpan.FromSeconds(1.5f);
|
||||
}
|
||||
56
Content.Shared/_White/Item/Telebaton/TelebatonSystem.cs
Normal file
56
Content.Shared/_White/Item/Telebaton/TelebatonSystem.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Item.ItemToggle.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
|
||||
namespace Content.Shared._White.Item.Telebaton;
|
||||
|
||||
public sealed class TelebatonSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TelebatonComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<TelebatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt);
|
||||
SubscribeLocalEvent<TelebatonComponent, ItemToggledEvent>(ToggleDone);
|
||||
SubscribeLocalEvent<TelebatonComponent, StaminaMeleeHitEvent>(OnHit);
|
||||
}
|
||||
|
||||
private void OnHit(Entity<TelebatonComponent> ent, ref StaminaMeleeHitEvent args)
|
||||
{
|
||||
var time = ent.Comp.KnockdownTime;
|
||||
if (time <= TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
foreach (var (uid, _) in args.HitList)
|
||||
{
|
||||
_stun.TryKnockdown(uid, time, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStaminaHitAttempt(Entity<TelebatonComponent> entity, ref StaminaDamageOnHitAttemptEvent args)
|
||||
{
|
||||
if (!_itemToggle.IsActivated(entity.Owner))
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<TelebatonComponent> entity, ref ExaminedEvent args)
|
||||
{
|
||||
var onMsg = _itemToggle.IsActivated(entity.Owner)
|
||||
? Loc.GetString("comp-telebaton-examined-on")
|
||||
: Loc.GetString("comp-telebaton-examined-off");
|
||||
args.PushMarkup(onMsg);
|
||||
}
|
||||
|
||||
private void ToggleDone(Entity<TelebatonComponent> entity, ref ItemToggledEvent args)
|
||||
{
|
||||
_item.SetHeldPrefix(entity.Owner, args.Activated ? "on" : "off");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user