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
This commit is contained in:
Red
2025-09-06 00:59:58 +03:00
committed by GitHub
parent 2ae9ccbf15
commit 963bdc022f
468 changed files with 2744 additions and 9687 deletions

View File

@@ -66,13 +66,19 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs
/// </remarks>
public bool IsHit = true;
public MeleeHitEvent(List<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage, Vector2? direction)
/// <summary>
/// CP14 Heavy attack flag.
/// </summary>
public bool CP14Heavy;
public MeleeHitEvent(List<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage, Vector2? direction, bool heavy = false)
{
HitEntities = hitEntities;
User = user;
Weapon = weapon;
BaseDamage = baseDamage;
Direction = direction;
CP14Heavy = heavy; //CP14
}
}

View File

@@ -84,7 +84,7 @@ public sealed partial class MeleeWeaponComponent : Component
/// Multiplies damage by this amount for single-target attacks.
/// </summary>
[DataField, AutoNetworkedField]
public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1);
public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1.3); //CP14 default bonus damage
// TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2
/// <summary>
@@ -131,7 +131,7 @@ public sealed partial class MeleeWeaponComponent : Component
/// CrystallEdge Melee upgrade. how far away from the player the animation should be played.
/// </summary>
[DataField]
public float CPAnimationOffset = -1f;
public float CPAnimationOffset = 1f;
// Sounds

View File

@@ -498,7 +498,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
LogImpact.Low,
$"{ToPrettyString(user):actor} melee attacked (light) using {ToPrettyString(meleeUid):tool} and missed");
}
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, null);
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, null, heavy: false);
RaiseLocalEvent(meleeUid, missEvent);
_meleeSound.PlaySwingSound(user, meleeUid, component);
return;
@@ -507,7 +507,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
// 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);
var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage, null, heavy: false);
RaiseLocalEvent(meleeUid, hitEvent);
if (hitEvent.Handled)
@@ -600,7 +600,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
LogImpact.Low,
$"{ToPrettyString(user):actor} melee attacked (heavy) using {ToPrettyString(meleeUid):tool} and missed");
}
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, direction);
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, direction, heavy: true);
RaiseLocalEvent(meleeUid, missEvent);
// immediate audio feedback
@@ -649,7 +649,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
// Raise event before doing damage so we can cancel damage if the event is handled
var hitEvent = new MeleeHitEvent(targets, user, meleeUid, damage, direction);
var hitEvent = new MeleeHitEvent(targets, user, meleeUid, damage, direction, heavy: true);
RaiseLocalEvent(meleeUid, hitEvent);
if (hitEvent.Handled)

View File

@@ -0,0 +1,7 @@
using Content.Shared.Actions;
namespace Content.Shared._CP14.Eye;
public sealed partial class CP14EyeOffsetToggleActionEvent : InstantActionEvent
{
}

View File

@@ -0,0 +1,27 @@
using Content.Shared.Stunnable;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellKnockdown : CP14SpellEffect
{
[DataField]
public float ThrowPower = 10f;
[DataField]
public TimeSpan Time = TimeSpan.FromSeconds(1f);
[DataField]
public bool DropItems = false;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null || args.User is null)
return;
var targetEntity = args.Target.Value;
var stun = entManager.System<SharedStunSystem>();
stun.TryKnockdown(args.Target.Value, Time, true, true, DropItems);
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// Adds bonus damage to weapons if targets are at a certain distance from the attacker.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class CP14BonusDistanceMeleeDamageComponent : Component
{
[DataField]
public DamageSpecifier BonusDamage = new();
[DataField]
public float MinDistance = 1f;
[DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/_CP14/Effects/critical.ogg")
{
Params = AudioParams.Default.WithVariation(0.125f),
};
[DataField]
public EntProtoId VFX = "CP14MeleeCritEffect";
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// After several wide attacks, a light attack deals additional damage.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CP14ComboBonusMeleeDamageComponent : Component
{
[DataField]
public DamageSpecifier BonusDamage = new();
[DataField]
public int HeavyAttackNeed = 2;
[DataField, AutoNetworkedField]
public int CurrentHeavyAttacks = 0;
[DataField, AutoNetworkedField]
public HashSet<EntityUid> HitEntities = new();
[DataField]
public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/_CP14/Effects/critical_sword.ogg")
{
Params = AudioParams.Default.WithVariation(0.125f),
};
[DataField]
public EntProtoId VFX = "CP14MeleeCritEffect";
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// After several wide attacks, a light attack deals additional damage.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class CP14LightMeleeKnockdownComponent : Component
{
[DataField]
public float ThrowDistance = 0.5f;
[DataField]
public TimeSpan KnockdownTime = TimeSpan.FromSeconds(0.25f);
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Audio;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// allows this item to be knocked out of your hands by a successful parry
/// </summary>
[RegisterComponent]
public sealed partial class CP14MeleeParriableComponent : Component
{
[DataField]
public TimeSpan LastMeleeHit = TimeSpan.Zero;
[DataField]
public SoundSpecifier ParrySound = new SoundPathSpecifier("/Audio/_CP14/Effects/parry1.ogg", AudioParams.Default.WithVariation(0.2f));
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// attacks with this item may knock CP14ParriableComponent items out of your hand on a hit
/// </summary>
[RegisterComponent]
public sealed partial class CP14MeleeParryComponent : Component
{
[DataField]
public TimeSpan ParryWindow = TimeSpan.FromSeconds(1f);
[DataField]
public float ParryPower = 1f;
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.MeleeWeapon.Components;
/// <summary>
/// Using this weapon damages the wearer's stamina.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class CP14MeleeWeaponStaminaCostComponent : Component
{
[DataField]
public float Stamina = 10f;
}

View File

@@ -1,24 +0,0 @@
using Content.Shared._CP14.MeleeWeapon.Components;
using Content.Shared.Damage;
using Content.Shared.Weapons.Melee.Events;
namespace Content.Shared._CP14.MeleeWeapon.EntitySystems;
public sealed class CP14MeleeSelfDamageSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
public override void Initialize()
{
SubscribeLocalEvent<CP14MeleeSelfDamageComponent, MeleeHitEvent>(OnMeleeHit);
}
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);
}
}

View File

@@ -0,0 +1,210 @@
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);
}
}