* 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
211 lines
7.2 KiB
C#
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);
|
|
}
|
|
}
|