Files
crystall-punk-14/Content.Shared/_CP14/MeleeWeapon/EntitySystems/CP14MeleeWeaponSystem.cs
Red 963bdc022f Rethinking equipment (#1711)
* delete all modular content

* clean up

* Update guard.yml

* spear first pass

* Add imperial two-handed sword and update spear

Introduces the imperial two-handed sword with new sprites, prototype, and animations. Refines spear configuration: adjusts damage, animation, and offsets, and updates its sprites and prototype to use a new resource path. Improves melee weapon animation logic for both thrust and slash attacks.

* neck displacement

* Update neck.png

* Update migration.yml

* dagger

* Add sword prototype and refactor melee weapon swing logic

Introduces a new sword entity and associated textures. Refactors melee weapon swing logic by renaming SwingLeft to CPSwingLeft and updating related code. Adjusts animation offsets and rotation values for daggers, spears, and two-handed swords. Moves and updates dagger texture assets to a new directory structure.

* hatchet

* sickle

* Add skimitar sword entity and sprites

Introduced a new 'skimitar' sword entity with description and associated sprite assets, including icon, in-hand, and equipped states. Also updated the imperial sword's name and description for clarity.

* Add rapier weapon and adjust melee weapon balance

Introduced the imperial rapier weapon with associated prototype and textures. Increased the default single-target melee damage modifier to 1.3 and removed per-weapon clickDamageModifier overrides from dagger, spear, sword, and two-handed sword. Also increased sword base damage to 10 for better balance.

* Add iron tool/weapon variants and update wall thresholds

Introduces iron variants for pickaxe, shovel, dagger, rapier, spear, sword, and two-handed sword, including new sprites and YAML prototypes. Adjusts wall and ore vein damage thresholds for destruction and sound triggers. Updates migration.yml to map modular iron weapons to new iron variants and spawns stone blocks on stonebrick wall breakage. Also refactors dagger textures to use an 'iron' directory.

* Refactor ice dagger and adjust blacksmith skills

Replaced CP14IceDagger with CP14WeaponDaggerIce, updating its parent, stats, and components for consistency. Adjusted base dagger damage types. Blacksmith job and related melting skills are now commented out or disabled, reflecting a change in skill progression and job setup.

* Update ice_dagger.yml

* Deprecate sword mastery skills and update melee swing logic

Commented out SwordMastery, RapierMastery, and SkimitarMastery skills and removed their assignment from guard and artisan job prototypes. Renamed CPSwingLeft to SwingLeft in melee weapon code for clarity and updated related logic.

* Remove requiredSkills from anvil and furnace recipes

Eliminated the 'requiredSkills' field from all recipes in Anvil/misc.yml and furnace.yml. This simplifies recipe definitions and removes skill prerequisites for crafting these items.

* Update guard_commander.yml

* Comment out freeLearnedSkills for T1 and T2 skeletons

Disabled the freeLearnedSkills entries, including SwordMastery and SkimitarMastery, in both T1 and T2 DemiplaneAntag skeleton YAML prototypes. This change may be for balancing or testing purposes.

* Update migration.yml

* Update migration.yml

* guidebook

* r

* spear passive + hammer passive

* tool hammer + skimitar refactor

* balance tweak

* kick nerf

* TOWER DEFENCE UPDATE

* default shield refactor

* buckler (only sprites)

* Update migration.yml

* buckler parry

* some fixes

* Update T2.yml

* Update T2.yml

* Update instruments.yml

* Update migration.yml

* M O P

* war axe

* Update migration.yml

* Keen Eye skill

* arrows + bow + loadouts

* Update tools.yml

* trading

* fix

* Update misc.yml

* Update migration.yml
2025-09-06 00:59:58 +03:00

211 lines
7.2 KiB
C#

using System.Numerics;
using Content.Shared._CP14.MeleeWeapon.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Systems;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._CP14.MeleeWeapon.EntitySystems;
public sealed class CP14MeleeWeaponSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly ThrowingSystem _throw = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14MeleeSelfDamageComponent, MeleeHitEvent>(OnMeleeHit);
SubscribeLocalEvent<CP14BonusDistanceMeleeDamageComponent, MeleeHitEvent>(OnDistanceBonusDamage);
SubscribeLocalEvent<CP14ComboBonusMeleeDamageComponent, MeleeHitEvent>(OnComboBonusDamage);
SubscribeLocalEvent<CP14LightMeleeKnockdownComponent, MeleeHitEvent>(OnKnockdownAttack);
SubscribeLocalEvent<CP14MeleeParryComponent, MeleeHitEvent>(OnMeleeParryHit);
SubscribeLocalEvent<CP14MeleeParriableComponent, AttemptMeleeEvent>(OnMeleeParriableHitAttmpt);
SubscribeLocalEvent<CP14MeleeWeaponStaminaCostComponent, MeleeHitEvent>(OnMeleeStaminaCost);
}
private void OnMeleeStaminaCost(Entity<CP14MeleeWeaponStaminaCostComponent> ent, ref MeleeHitEvent args)
{
_stamina.TakeStaminaDamage(args.User, ent.Comp.Stamina);
}
private void OnMeleeParryHit(Entity<CP14MeleeParryComponent> ent, ref MeleeHitEvent args)
{
if (args.HitEntities.Count != 1)
return;
var target = args.HitEntities[0];
var activeTargetHand = _hands.GetActiveHand(target);
var heldItem = _hands.GetHeldItem(target, activeTargetHand);
if (heldItem is null)
return;
if (!TryComp<CP14MeleeParriableComponent>(heldItem, out var meleeParriable))
return;
if (_timing.CurTime > meleeParriable.LastMeleeHit + ent.Comp.ParryWindow)
return;
_hands.TryDrop(target, heldItem.Value);
_throw.TryThrow(heldItem.Value, _random.NextAngle().ToWorldVec(), ent.Comp.ParryPower, target);
_popup.PopupPredicted( Loc.GetString("cp14-successful-parry"), args.User, args.User);
_audio.PlayPredicted(meleeParriable.ParrySound, heldItem.Value, args.User);
}
private void OnMeleeParriableHitAttmpt(Entity<CP14MeleeParriableComponent> ent, ref AttemptMeleeEvent args)
{
ent.Comp.LastMeleeHit = _timing.CurTime;
}
private void OnKnockdownAttack(Entity<CP14LightMeleeKnockdownComponent> ent, ref MeleeHitEvent args)
{
if (args.CP14Heavy)
return;
foreach (var hit in args.HitEntities)
{
_stun.TryKnockdown(hit, ent.Comp.KnockdownTime, true, drop: false);
// Vector from splitter to item
var direction = Transform(hit).Coordinates.Position - Transform(args.User).Coordinates.Position;
if (direction != Vector2.Zero)
{
var dir = direction.Normalized() * ent.Comp.ThrowDistance;
_throw.TryThrow(hit, dir, 3);
}
}
}
private void OnComboBonusDamage(Entity<CP14ComboBonusMeleeDamageComponent> ent, ref MeleeHitEvent args)
{
// Resets combo state
void Reset()
{
ent.Comp.HitEntities.Clear();
ent.Comp.CurrentHeavyAttacks = 0;
Dirty(ent);
}
// No hits this swing → reset
if (args.HitEntities.Count == 0)
{
Reset();
return;
}
var comp = ent.Comp;
// Not enough heavy attacks accumulated yet
if (comp.CurrentHeavyAttacks < comp.HeavyAttackNeed)
{
// Light attack before threshold → reset combo
if (!args.CP14Heavy)
{
Reset();
return;
}
// Heavy attack: track overlapping targets across swings
if (comp.HitEntities.Count == 0)
{
// First heavy: initialize the set with current hits
comp.HitEntities.UnionWith(args.HitEntities);
}
else
{
// Subsequent heavy: keep only targets hit every time
comp.HitEntities.IntersectWith(args.HitEntities);
// Diverged to different targets → reset
if (comp.HitEntities.Count == 0)
{
Reset();
return;
}
}
comp.CurrentHeavyAttacks++;
Dirty(ent);
return;
}
// Light attack after enough heavies → check if it hits any tracked target
if (comp.HitEntities.Overlaps(args.HitEntities) && !args.CP14Heavy)
{
if (_timing.IsFirstTimePredicted)
{
_audio.PlayPredicted(comp.Sound, ent, args.User);
args.BonusDamage += comp.BonusDamage;
// Visual feedback on every hit entity this swing
foreach (var hit in args.HitEntities)
{
PredictedSpawnAtPosition(comp.VFX, Transform(hit).Coordinates);
}
}
Reset();
}
}
private void OnDistanceBonusDamage(Entity<CP14BonusDistanceMeleeDamageComponent> ent, ref MeleeHitEvent args)
{
var critical = true;
if (args.HitEntities.Count == 0)
return;
var userPos = _transform.GetWorldPosition(args.User);
//Crit only if all targets are at distance
foreach (var hit in args.HitEntities)
{
var targetPos = _transform.GetWorldPosition(hit);
var distance = (userPos - targetPos).Length();
if (distance < ent.Comp.MinDistance)
{
critical = false;
break;
}
}
if (!critical)
return;
if (!_timing.IsFirstTimePredicted)
return;
_audio.PlayPredicted(ent.Comp.Sound, ent, args.User);
args.BonusDamage += ent.Comp.BonusDamage;
//Visual effect!
foreach (var hit in args.HitEntities)
{
PredictedSpawnAtPosition(ent.Comp.VFX, Transform(hit).Coordinates);
}
}
private void OnMeleeHit(Entity<CP14MeleeSelfDamageComponent> ent, ref MeleeHitEvent args)
{
if (!args.IsHit)
return;
if (args.HitEntities.Count == 0)
return;
_damageable.TryChangeDamage(ent, ent.Comp.DamageToSelf);
}
}