forked from Simple-Station/Einstein-Engines
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The Throwing Update (Simple-Station#1307)
# Description Turns a plethora of items into throwing weapons that deal damage when thrown. Throwing weapons cost stamina to throw. ## Technical/Balance Details To make a melee weapon also a throwing weapon, just add `- type: DamageOtherOnHit`, and it will automatically inherit the damage from a light melee attack and the melee sound effect as the thrown hit sound effect. You can set a custom damage value with the `damage` field (necessary when the item is not a `MeleeWeapon`) and stamina cost with `staminaCost`. To make the throwing weapon embed and deal damage over time when embedded, add `- type: EmbeddableProjectile` and `- type: EmbedPassiveDamage`. By default, the embed damage per second is 5% of the throwing damage, but it can be modified on `EmbedPassiveDamage` with `throwingDamageMultiplier`. The default stamina cost for throwing is 3.5 stamina. The baseline cost for almost all DoT embeddables is 5 stamina, because of the extra damage the DoT brings. When a thrown item hits a target with body parts, it will randomly select a body part and only deal throwing damage to that body part. It will also embed to the same body part and only deal passive embed damage to it. ## TODO The unchecked checkmarks are best addressed in another PR but they will stay here for now. <details><summary>Show Todo</summary> - [ ] Deal with prediction issue on embeddable projectile removal - [ ] This happens even before this PR so not really a big issue, maybe in a separate PR - [x] Add embeddable damage numbers to embeddables - [x] Fix throwing angle for surgery tools after the surgical tools sprite update - [ ] Try to make the throw knockback function as if it hit a wall - [x] Esword/desword/e-dagger toggle embed damage - [x] Don't start passive embed damage if EmbedPassiveDamageComponent has no damage - [x] Make DamageOtherOnHit.Damage not nullable - [x] Throwing damage only to a specific body part ### Traits - [ ] **Enraged Throw** (Oni) - [ ] Oni/trait damage bonus applied to throwing weapon too - [ ] Can throw carried bodies, which will do a MassContest between the thrown body and the hit body to determine blunt damage, and stun duration for each party - [ ] 15% resistance to thrown/embed damage - This helps when their enemy uses the items they throw against them. - [ ] **Sharpthrower** (Human) - [ ] 10% more Brute thrown damage - [ ] 50% chance of throw hitting targetted body part - [ ] 40% throwing stamina cost reduction - [ ] 15% resistance to thrown/embed damage ### Embeds - [x] Adjust embed damage per second to be like /tg/ (in /tg/ spear has around ~1.2 embed DPS, adjust for ~45% embed chance since we're not implementing embed chance and its 0.54) - [ ] Merge EmbeddableProjectileComponent and EmbeddablePassiveDamageComponent - [ ] Split SharedProjectileSystem into EmbeddableProjectileSystem - [x] Embed to a specific body part and deal damage only to that part, for now can randomly select body parts on embed - [ ] ~~Normal passive damage becomes x0.2 when lying down~~ - [ ] Increased damage when moving, more bonus damage for running (Jostle DPS on /tg/ is 0.2 running and 0.1 when walking/crawling) - [x] All embeddables have a fall out time (30 or 45 secs) - [ ] - [x] On damage examine, can see that an object is embeddable "It can embed on a target if thrown." - [ ] Negative moodlet for attached harmful embeddables - [ ] On health examine target with embeds, can see embedded objects "He has a spear embedded in his left arm." - [x] On examine item that is embedded, can see to which body part the item is embedded "The spear is embedded on Urist McHands's left arm." - [ ] An embeddable removed outside of surgery deals a lot of damage (x2 thrown damage) - [ ] Lying down prevents natural falling out and thus the damage with non-surgical removal - [ ] Surgical procedure on a body part to remove all embeds on it, using hemostat for removal - [x] Allow anyone to remove embedded cultist weapons even if they're not a cultist </details> ## Media **Throwing Toolbox Tools** https://github.com/user-attachments/assets/4e20568f-adf0-4be8-ac38-fc6b21fed03c **Examine** ![image](https://github.com/user-attachments/assets/ef95e653-1491-4d9b-8f84-785c3df22763) **Examine After Embedding** ![image](https://github.com/user-attachments/assets/edc79c8f-db23-4bd3-9fa7-3b47f79c5881) ## Changelog :cl: Skubman - add: The Throwing Update is here. You can throw most melee weapons at the cost of stamina to deal damage from afar. - add: Dozens of throwable weapons, mainly sharp weapons will now embed on throw and deal damage every second until they're manually removed or naturally fall off after some time. - add: Examining the damage values of an item now shows its throwing damage, throwing stamina cost, whether or not it embeds on a throw, and if the embed deals damage over time. - add: Examining an embedded item now shows what body part it's embedded in. - tweak: The traits High Adrenaline, Adrenal Dysfunction, Masochism and Low Pain Tolerance now affect throwing attacks just like melee attacks. - tweak: The default time to remove embedded items has been increased from 3 to 5 seconds, and both the remover and the target with the embedded item need to stand still during the removal. - tweak: The time to pry up a floor tile with a crowbar and other tools has been decreased from 1 second to 0.5 seconds. The throwing damage of floor tiles has been increased. Go figure. - fix: Attempting to throw a Blood Cultist item without being a cultist will stun you and drop the item you're holding properly. --------- Co-authored-by: sleepyyapril <[email protected]>
- Loading branch information
1 parent
3ae4370
commit c40af73
Showing
105 changed files
with
1,773 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
using Content.Shared.Damage.Systems; | ||
|
||
namespace Content.Client.Damage; | ||
|
||
public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem; |
19 changes: 0 additions & 19 deletions
19
Content.Server/Damage/Components/DamageOtherOnHitComponent.cs
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,66 @@ | ||
using Content.Server.Administration.Logs; | ||
using Content.Server.Damage.Components; | ||
using Content.Server.Weapons.Ranged.Systems; | ||
using Content.Shared.Camera; | ||
using Content.Shared.Damage; | ||
using Content.Shared.Damage.Components; | ||
using Content.Shared.Damage.Events; | ||
using Content.Shared.Damage.Systems; | ||
using Content.Shared.Database; | ||
using Content.Shared.Effects; | ||
using Content.Shared.Item.ItemToggle.Components; | ||
using Content.Shared.Mobs.Components; | ||
using Content.Shared.Projectiles; | ||
using Content.Shared.Popups; | ||
using Content.Shared.Throwing; | ||
using Content.Shared.Weapons.Melee; | ||
using Robust.Server.GameObjects; | ||
using Robust.Shared.Audio; | ||
using Robust.Shared.Physics.Components; | ||
using Robust.Shared.Player; | ||
using Robust.Shared.Prototypes; | ||
using Robust.Shared.Utility; | ||
|
||
namespace Content.Server.Damage.Systems | ||
{ | ||
public sealed class DamageOtherOnHitSystem : EntitySystem | ||
public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem | ||
{ | ||
[Dependency] private readonly IAdminLogManager _adminLogger = default!; | ||
[Dependency] private readonly GunSystem _guns = default!; | ||
[Dependency] private readonly DamageableSystem _damageable = default!; | ||
[Dependency] private readonly DamageExamineSystem _damageExamine = default!; | ||
[Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; | ||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; | ||
[Dependency] private readonly ThrownItemSystem _thrownItem = default!; | ||
[Dependency] private readonly SharedPopupSystem _popup = default!; | ||
[Dependency] private readonly StaminaSystem _stamina = default!; | ||
|
||
public override void Initialize() | ||
{ | ||
SubscribeLocalEvent<DamageOtherOnHitComponent, ThrowDoHitEvent>(OnDoHit); | ||
base.Initialize(); | ||
|
||
SubscribeLocalEvent<StaminaComponent, BeforeThrowEvent>(OnBeforeThrow); | ||
SubscribeLocalEvent<DamageOtherOnHitComponent, DamageExamineEvent>(OnDamageExamine); | ||
} | ||
|
||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args) | ||
private void OnBeforeThrow(EntityUid uid, StaminaComponent component, ref BeforeThrowEvent args) | ||
{ | ||
var dmg = _damageable.TryChangeDamage(args.Target, component.Damage, component.IgnoreResistances, origin: args.Component.Thrower); | ||
|
||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying. | ||
if (dmg != null && HasComp<MobStateComponent>(args.Target)) | ||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision"); | ||
|
||
if (dmg is { Empty: false }) | ||
{ | ||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager)); | ||
} | ||
if (!TryComp<DamageOtherOnHitComponent>(args.ItemUid, out var damage)) | ||
return; | ||
|
||
_guns.PlayImpactSound(args.Target, dmg, null, false); | ||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) | ||
if (component.CritThreshold - component.StaminaDamage <= damage.StaminaCost) | ||
{ | ||
var direction = body.LinearVelocity.Normalized(); | ||
_sharedCameraRecoil.KickCamera(args.Target, direction); | ||
args.Cancelled = true; | ||
_popup.PopupEntity(Loc.GetString("throw-no-stamina", ("item", args.ItemUid)), uid, uid); | ||
return; | ||
} | ||
|
||
// TODO: If more stuff touches this then handle it after. | ||
if (TryComp<PhysicsComponent>(uid, out var physics)) | ||
{ | ||
_thrownItem.LandComponent(args.Thrown, args.Component, physics, false); | ||
} | ||
_stamina.TakeStaminaDamage(uid, damage.StaminaCost, component, visual: false); | ||
} | ||
|
||
private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args) | ||
{ | ||
_damageExamine.AddDamageExamine(args.Message, component.Damage, Loc.GetString("damage-throw")); | ||
_damageExamine.AddDamageExamine(args.Message, GetDamage(uid, component, args.User), Loc.GetString("damage-throw")); | ||
|
||
if (component.StaminaCost == 0) | ||
return; | ||
|
||
var staminaCostMarkup = FormattedMessage.FromMarkupOrThrow( | ||
Loc.GetString("damage-stamina-cost", | ||
("type", Loc.GetString("damage-throw")), ("cost", component.StaminaCost))); | ||
args.Message.PushNewline(); | ||
args.Message.AddMessage(staminaCostMarkup); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
Content.Shared/Damage/Components/DamageOtherOnHitComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
using Content.Shared.Contests; | ||
using Content.Shared.Damage.Systems; | ||
using Content.Shared.Damage; | ||
using Robust.Shared.Audio; | ||
using Robust.Shared.GameStates; | ||
using Robust.Shared.Prototypes; | ||
|
||
namespace Content.Shared.Damage.Components; | ||
|
||
/// <summary> | ||
/// Deals damage when thrown. | ||
/// </summary> | ||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] | ||
public sealed partial class DamageOtherOnHitComponent : Component | ||
{ | ||
[DataField, AutoNetworkedField] | ||
public bool IgnoreResistances = false; | ||
|
||
/// <summary> | ||
/// The damage that a throw deals. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public DamageSpecifier Damage = new(); | ||
|
||
/// <summary> | ||
/// The stamina cost of throwing this entity. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public float StaminaCost = 3.5f; | ||
|
||
/// <summary> | ||
/// The maximum amount of hits per throw before landing on the floor. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public int MaxHitQuantity = 1; | ||
|
||
/// <summary> | ||
/// The tracked amount of hits in a single throw. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public int HitQuantity = 0; | ||
|
||
/// <summary> | ||
/// The multiplier to apply to the entity's light attack damage to calculate the throwing damage. | ||
/// Only used if this component has a MeleeWeaponComponent and Damage is not set on the prototype. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public float MeleeDamageMultiplier = 1f; | ||
|
||
/// <summary> | ||
/// The sound to play when this entity hits on a throw. | ||
/// If null, attempts to retrieve the SoundHit from MeleeWeaponComponent. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public SoundSpecifier? SoundHit; | ||
|
||
/// <summary> | ||
/// Arguments for modifying the throwing weapon damage with contests. | ||
/// These are the same ContestArgs in MeleeWeaponComponent. | ||
/// </summary> | ||
[DataField] | ||
public ContestArgs ContestArgs = new ContestArgs | ||
{ | ||
DoStaminaInteraction = true, | ||
StaminaDisadvantage = true, | ||
DoHealthInteraction = true, | ||
}; | ||
|
||
/// <summary> | ||
/// Plays if no damage is done to the target entity. | ||
/// If null, attempts to retrieve the SoundNoDamage from MeleeWeaponComponent. | ||
/// </summary> | ||
[DataField, AutoNetworkedField] | ||
public SoundSpecifier SoundNoDamage { get; set; } = new SoundCollectionSpecifier("WeakHit"); | ||
} |
7 changes: 7 additions & 0 deletions
7
Content.Shared/Damage/Components/DamageOtherOnHitImmuneComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Content.Shared.Damage.Components; | ||
|
||
/// <summary> | ||
/// This is used for entities that are immune to getting hit by DamageOtherOnHit, and getting embedded from EmbeddableProjectile. | ||
/// </summary> | ||
[RegisterComponent] | ||
public sealed partial class DamageOtherOnHitImmuneComponent : Component {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using Content.Shared.Damage; | ||
using Content.Shared.Damage.Components; | ||
using Content.Shared.Item.ItemToggle.Components; | ||
|
||
namespace Content.Shared.Damage.Events; | ||
|
||
/// <summary> | ||
/// Raised on a throwing weapon to calculate potential damage bonuses or decreases. | ||
/// </summary> | ||
[ByRefEvent] | ||
public record struct GetThrowingDamageEvent(EntityUid Weapon, DamageSpecifier Damage, List<DamageModifierSet> Modifiers, EntityUid? User); | ||
|
||
/// <summary> | ||
/// Raised on a throwing weapon when DamageOtherOnHit has been successfully initialized. | ||
/// </summary> | ||
public record struct DamageOtherOnHitStartupEvent(Entity<DamageOtherOnHitComponent> Weapon); | ||
|
||
/// <summary> | ||
/// Raised on a throwing weapon when ItemToggleDamageOtherOnHit has been successfully initialized. | ||
/// </summary> | ||
public record struct ItemToggleDamageOtherOnHitStartupEvent(Entity<ItemToggleDamageOtherOnHitComponent> Weapon); |
Oops, something went wrong.