Proto-kinetic crusher (#16277)

Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
This commit is contained in:
metalgearsloth
2023-05-14 13:15:18 +10:00
committed by GitHub
parent 356bf96039
commit 6417bb4fa0
68 changed files with 926 additions and 312 deletions

View File

@@ -1,48 +1,49 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
namespace Content.Client.Markers
namespace Content.Client.Markers;
public sealed class MarkerSystem : EntitySystem
{
public sealed class MarkerSystem : EntitySystem
private bool _markersVisible;
public bool MarkersVisible
{
private bool _markersVisible;
public bool MarkersVisible
get => _markersVisible;
set
{
get => _markersVisible;
set
{
_markersVisible = value;
UpdateMarkers();
}
_markersVisible = value;
UpdateMarkers();
}
}
public override void Initialize()
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MarkerComponent, ComponentStartup>(OnStartup);
}
private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args)
{
UpdateVisibility(uid);
}
private void UpdateVisibility(EntityUid uid)
{
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
{
base.Initialize();
SubscribeLocalEvent<MarkerComponent, ComponentStartup>(OnStartup);
sprite.Visible = MarkersVisible;
}
}
private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args)
{
UpdateVisibility(marker);
}
private void UpdateMarkers()
{
var query = AllEntityQuery<MarkerComponent>();
private void UpdateVisibility(MarkerComponent marker)
while (query.MoveNext(out var uid, out var comp))
{
if (EntityManager.TryGetComponent(marker.Owner, out SpriteComponent? sprite))
{
sprite.Visible = MarkersVisible;
}
}
private void UpdateMarkers()
{
foreach (var markerComponent in EntityManager.EntityQuery<MarkerComponent>(true))
{
UpdateVisibility(markerComponent);
}
UpdateVisibility(uid);
}
}
}

View File

@@ -14,7 +14,6 @@ public sealed class ProjectileSystem : SharedProjectileSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ProjectileComponent, ComponentHandleState>(OnHandleState);
SubscribeNetworkEvent<ImpactEffectEvent>(OnProjectileImpact);
}
@@ -54,11 +53,4 @@ public sealed class ProjectileSystem : SharedProjectileSystem
_player.Play(ent, anim, "impact-effect");
}
}
private void OnHandleState(EntityUid uid, ProjectileComponent component, ref ComponentHandleState args)
{
if (args.Current is not ProjectileComponentState state) return;
component.Shooter = state.Shooter;
component.IgnoreShooter = state.IgnoreShooter;
}
}

View File

@@ -13,7 +13,7 @@ namespace Content.Client.Toggleable;
public sealed class ToggleableLightVisualsComponent : Component
{
/// <summary>
/// Sprite layer that will have it's visibility toggled when this item is toggled.
/// Sprite layer that will have its visibility toggled when this item is toggled.
/// </summary>
[DataField("spriteLayer")]
public string SpriteLayer = "light";

View File

@@ -0,0 +1,39 @@
using Content.Shared.Weapons.Marker;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client.Weapons.Marker;
public sealed class DamageMarkerSystem : SharedDamageMarkerSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DamageMarkerComponent, ComponentStartup>(OnMarkerStartup);
SubscribeLocalEvent<DamageMarkerComponent, ComponentShutdown>(OnMarkerShutdown);
}
private void OnMarkerStartup(EntityUid uid, DamageMarkerComponent component, ComponentStartup args)
{
if (!_timing.ApplyingState || component.Effect == null || !TryComp<SpriteComponent>(uid, out var sprite))
return;
var layer = sprite.LayerMapReserveBlank(DamageMarkerKey.Key);
sprite.LayerSetState(layer, component.Effect.RsiState, component.Effect.RsiPath);
}
private void OnMarkerShutdown(EntityUid uid, DamageMarkerComponent component, ComponentShutdown args)
{
if (!_timing.ApplyingState || !TryComp<SpriteComponent>(uid, out var sprite) || !sprite.LayerMapTryGet(DamageMarkerKey.Key, out var weh))
return;
sprite.RemoveLayer(weh);
}
private enum DamageMarkerKey : byte
{
Key
}
}

View File

@@ -6,6 +6,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.StatusEffect;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -85,6 +86,16 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
// Heavy attack.
if (altDown == BoundKeyState.Down)
{
// TODO: Need to make alt-fire melee its own component I guess?
// Melee and guns share a lot in the middle but share virtually nothing at the start and end so
// it's kinda tricky.
// I think as long as we make secondaries their own component it's probably fine
// as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with.
if (HasComp<GunComponent>(weaponUid))
{
return;
}
// We did the click to end the attack but haven't pulled the key up.
if (weapon.Attacking)
{

View File

@@ -1,6 +1,7 @@
using Content.Client.Items;
using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera;
using Content.Shared.Input;
using Content.Shared.Spawners.Components;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
@@ -137,7 +138,9 @@ public sealed partial class GunSystem : SharedGunSystem
return;
}
if (_inputSystem.CmdStates.GetState(EngineKeyFunctions.Use) != BoundKeyState.Down)
var useKey = gun.UseKey ? EngineKeyFunctions.Use : EngineKeyFunctions.UseSecondary;
if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down)
{
if (gun.ShotCounter != 0)
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = gunUid });
@@ -296,6 +299,9 @@ public sealed partial class GunSystem : SharedGunSystem
_animPlayer.Play(ent, anim, "muzzle-flash");
var light = EnsureComp<PointLightComponent>(uid);
if (light.Enabled)
return;
light.NetSyncEnabled = false;
light.Enabled = true;
light.Color = Color.FromHex("#cc8e2b");

View File

@@ -86,6 +86,6 @@ public sealed class ProjectileAnomalySystem : EntitySystem
comp.Damage *= severity;
_gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, component.MaxProjectileSpeed * severity);
_gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, uid, component.MaxProjectileSpeed * severity);
}
}

View File

@@ -6,5 +6,9 @@ namespace Content.Server.Gatherable.Components;
[RegisterComponent]
public sealed class GatheringProjectileComponent : Component
{
/// <summary>
/// How many more times we can gather.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("amount")]
public int Amount = 1;
}

View File

@@ -17,12 +17,16 @@ public sealed partial class GatherableSystem
{
if (!args.OtherFixture.Hard ||
args.OurFixture.ID != SharedProjectileSystem.ProjectileFixture ||
component.Amount <= 0 ||
!TryComp<GatherableComponent>(args.OtherEntity, out var gatherable))
{
return;
}
Gather(args.OtherEntity, uid, gatherable);
QueueDel(uid);
component.Amount--;
if (component.Amount <= 0)
QueueDel(uid);
}
}

View File

@@ -1,8 +1,5 @@
using Content.Server.Actions;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Light;
@@ -11,10 +8,7 @@ using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -37,7 +31,6 @@ namespace Content.Server.Light.EntitySystems
{
base.Initialize();
SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
@@ -45,41 +38,7 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent<HandheldLightComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<HandheldLightComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<HandheldLightComponent, ToggleActionEvent>(OnToggleAction);
SubscribeLocalEvent<HandheldLightComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<HandheldLightComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
}
private void OnEntInserted(
EntityUid uid,
HandheldLightComponent component,
EntInsertedIntoContainerMessage args)
{
// Not guaranteed to be the correct container for our slot, I don't care.
UpdateLevel(uid, component);
}
private void OnEntRemoved(
EntityUid uid,
HandheldLightComponent component,
EntRemovedFromContainerMessage args)
{
// Ditto above
UpdateLevel(uid, component);
}
private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetItemActionsEvent args)
{
if (component.ToggleAction == null
&& _proto.TryIndex(component.ToggleActionId, out InstantActionPrototype? act))
{
component.ToggleAction = new(act);
}
if (component.ToggleAction != null)
args.Actions.Add(component.ToggleAction);
}
private void OnToggleAction(EntityUid uid, HandheldLightComponent component, ToggleActionEvent args)
@@ -114,11 +73,6 @@ namespace Content.Server.Light.EntitySystems
return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, HandheldLightComponent.StatusLevels);
}
private void OnRemove(EntityUid uid, HandheldLightComponent component, ComponentRemove args)
{
_activeLights.Remove(component);
}
private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
@@ -144,36 +98,6 @@ namespace Content.Server.Light.EntitySystems
: Loc.GetString("handheld-light-component-on-examine-is-off-message"));
}
public override void Shutdown()
{
base.Shutdown();
_activeLights.Clear();
}
public override void Update(float frameTime)
{
var toRemove = new RemQueue<HandheldLightComponent>();
foreach (var handheld in _activeLights)
{
var uid = handheld.Owner;
if (handheld.Deleted)
{
toRemove.Add(handheld);
continue;
}
if (Paused(uid)) continue;
TryUpdate(uid, handheld, frameTime);
}
foreach (var light in toRemove)
{
_activeLights.Remove(light);
}
}
private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract)

View File

@@ -1,6 +1,7 @@
using Content.Server.PowerCell;
using Content.Shared.Interaction.Events;
using Content.Shared.Pinpointer;
using Content.Shared.PowerCell;
using Robust.Server.GameObjects;
using Robust.Shared.Timing;

View File

@@ -0,0 +1,91 @@
using Content.Server.Power.Components;
using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components;
namespace Content.Server.PowerCell;
public sealed partial class PowerCellSystem
{
/*
* Handles PowerCellDraw
*/
private static readonly TimeSpan Delay = TimeSpan.FromSeconds(1);
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
while (query.MoveNext(out var uid, out var comp, out var slot))
{
if (!comp.Drawing)
continue;
if (_timing.CurTime < comp.NextUpdateTime)
continue;
comp.NextUpdateTime += Delay;
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
continue;
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
comp.Drawing = false;
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
}
private void OnUnpaused(EntityUid uid, PowerCellDrawComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdateTime += args.PausedTime;
}
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
{
// Update the bools for client prediction.
bool canDraw;
bool canUse;
if (component.UseRate > 0f)
{
canUse = args.Charge > component.UseRate;
}
else
{
canUse = true;
}
if (component.DrawRate > 0f)
{
canDraw = args.Charge > 0f;
}
else
{
canDraw = true;
}
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
component.CanDraw = canDraw;
component.CanUse = canUse;
Dirty(component);
}
}
private void OnDrawCellChanged(EntityUid uid, PowerCellDrawComponent component, PowerCellChangedEvent args)
{
var canDraw = !args.Ejected && HasCharge(uid, float.MinValue);
var canUse = !args.Ejected && HasActivatableCharge(uid, component);
if (canUse != component.CanUse || canDraw != component.CanDraw)
{
component.CanDraw = canDraw;
component.CanUse = canUse;
Dirty(component);
}
}
}

View File

@@ -19,7 +19,10 @@ using Robust.Shared.Timing;
namespace Content.Server.PowerCell;
public sealed class PowerCellSystem : SharedPowerCellSystem
/// <summary>
/// Handles Power cells
/// </summary>
public sealed partial class PowerCellSystem : SharedPowerCellSystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -39,43 +42,19 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<PowerCellComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<PowerCellComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
SubscribeLocalEvent<PowerCellDrawComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
// funny
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
SubscribeLocalEvent<PowerCellSlotComponent, BeingMicrowavedEvent>(OnSlotMicrowaved);
SubscribeLocalEvent<BatteryComponent, BeingMicrowavedEvent>(OnMicrowaved);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<PowerCellDrawComponent, PowerCellSlotComponent>();
while (query.MoveNext(out var uid, out var comp, out var slot))
{
if (!comp.Enabled)
continue;
if (_timing.CurTime < comp.NextUpdateTime)
continue;
comp.NextUpdateTime += TimeSpan.FromSeconds(1);
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
continue;
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate, battery))
continue;
comp.Enabled = false;
var ev = new PowerCellSlotEmptyEvent();
RaiseLocalEvent(uid, ref ev);
}
}
private void OnRejuvenate(EntityUid uid, PowerCellComponent component, RejuvenateEvent args)
{
component.IsRigged = false;
@@ -83,13 +62,13 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
private void OnSlotMicrowaved(EntityUid uid, PowerCellSlotComponent component, BeingMicrowavedEvent args)
{
if (_itemSlotsSystem.TryGetSlot(uid, component.CellSlotId, out ItemSlot? slot))
{
if (slot.Item == null)
return;
if (!_itemSlotsSystem.TryGetSlot(uid, component.CellSlotId, out var slot))
return;
RaiseLocalEvent(slot.Item.Value, args);
}
if (slot.Item == null)
return;
RaiseLocalEvent(slot.Item.Value, args);
}
private void OnMicrowaved(EntityUid uid, BatteryComponent component, BeingMicrowavedEvent args)
@@ -111,17 +90,14 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
return;
}
if (!TryComp(uid, out AppearanceComponent? appearance))
return;
var frac = args.Charge / args.MaxCharge;
var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels);
_sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level, appearance);
_sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level);
// If this power cell is inside a cell-slot, inform that entity that the power has changed (for updating visuals n such).
if (_containerSystem.TryGetContainingContainer(uid, out var container)
&& TryComp(container.Owner, out PowerCellSlotComponent? slot)
&& _itemSlotsSystem.TryGetSlot(container.Owner, slot.CellSlotId, out ItemSlot? itemSlot))
&& _itemSlotsSystem.TryGetSlot(container.Owner, slot.CellSlotId, out var itemSlot))
{
if (itemSlot.Item == uid)
RaiseLocalEvent(container.Owner, new PowerCellChangedEvent(false));
@@ -136,11 +112,6 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
RaiseLocalEvent(uid, ref ev);
}
private void OnUnpaused(EntityUid uid, PowerCellDrawComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdateTime += args.PausedTime;
}
private void Explode(EntityUid uid, BatteryComponent? battery = null, EntityUid? cause = null)
{
if (!Resolve(uid, ref battery))
@@ -190,10 +161,10 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
public void SetPowerCellDrawEnabled(EntityUid uid, bool enabled, PowerCellDrawComponent? component = null)
{
if (!Resolve(uid, ref component, false))
if (!Resolve(uid, ref component, false) || enabled == component.Drawing)
return;
component.Enabled = enabled;
component.Drawing = enabled;
component.NextUpdateTime = _timing.CurTime;
}

View File

@@ -26,12 +26,6 @@ public sealed class ProjectileSystem : SharedProjectileSystem
{
base.Initialize();
SubscribeLocalEvent<ProjectileComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<ProjectileComponent, ComponentGetState>(OnGetState);
}
private void OnGetState(EntityUid uid, ProjectileComponent component, ref ComponentGetState args)
{
args.State = new ProjectileComponentState(component.Shooter, component.IgnoreShooter);
}
private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref StartCollideEvent args)

View File

@@ -1,4 +1,5 @@
using Content.Server.PowerCell;
using Content.Shared.PowerCell;
using Content.Shared.UserInterface;
namespace Content.Server.UserInterface;

View File

@@ -0,0 +1,8 @@
using Content.Shared.Weapons.Marker;
namespace Content.Server.Weapons;
public sealed class DamageMarkerSystem : SharedDamageMarkerSystem
{
}

View File

@@ -118,7 +118,7 @@ public sealed partial class GunSystem : SharedGunSystem
// pneumatic cannon doesn't shoot bullets it just throws them, ignore ammo handling
if (throwItems && ent != null)
{
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, user);
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user);
continue;
}
@@ -136,14 +136,14 @@ public sealed partial class GunSystem : SharedGunSystem
for (var i = 0; i < cartridge.Count; i++)
{
var uid = Spawn(cartridge.Prototype, fromEnt);
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, user);
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
shotProjectiles.Add(uid);
}
}
else
{
var uid = Spawn(cartridge.Prototype, fromEnt);
ShootOrThrow(uid, mapDirection, gunVelocity, gun, user);
ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
shotProjectiles.Add(uid);
}
@@ -175,7 +175,7 @@ public sealed partial class GunSystem : SharedGunSystem
shotProjectiles.Add(ent!.Value);
MuzzleFlash(gunUid, newAmmo, user);
Audio.PlayPredicted(gun.SoundGunshot, gunUid, user);
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, user);
ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user);
break;
case HitscanPrototype hitscan:
@@ -265,7 +265,7 @@ public sealed partial class GunSystem : SharedGunSystem
});
}
private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid? user)
private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user)
{
// Do a throw
if (!HasComp<ProjectileComponent>(uid))
@@ -276,10 +276,10 @@ public sealed partial class GunSystem : SharedGunSystem
return;
}
ShootProjectile(uid, mapDirection, gunVelocity, user, gun.ProjectileSpeed);
ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeed);
}
public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid? user = null, float speed = 20f)
public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocity, EntityUid gunUid, EntityUid? user = null, float speed = 20f)
{
var physics = EnsureComp<PhysicsComponent>(uid);
Physics.SetBodyStatus(physics, BodyStatus.InAir);
@@ -293,6 +293,7 @@ public sealed partial class GunSystem : SharedGunSystem
{
var projectile = EnsureComp<ProjectileComponent>(uid);
Projectiles.SetShooter(projectile, user.Value);
projectile.Weapon = gunUid;
}
TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle());

View File

@@ -1,16 +0,0 @@
namespace Content.Shared.Interaction.Events;
/// <summary>
/// Raised on directed a weapon when being used in a melee attack.
/// </summary>
[ByRefEvent]
public struct MeleeAttackAttemptEvent
{
public bool Cancelled = false;
public readonly EntityUid User;
public MeleeAttackAttemptEvent(EntityUid user)
{
User = user;
}
}

View File

@@ -1,15 +1,35 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.PowerCell;
namespace Content.Shared.PowerCell;
/// <summary>
/// Indicates that the entity's ActivatableUI requires power or else it closes.
/// </summary>
[RegisterComponent, Access(typeof(PowerCellSystem))]
public sealed class PowerCellDrawComponent : Component
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class PowerCellDrawComponent : Component
{
#region Prediction
/// <summary>
/// Whether there is any charge available to draw.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("canDraw"), AutoNetworkedField]
public bool CanDraw;
/// <summary>
/// Whether there is sufficient charge to use.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("canUse"), AutoNetworkedField]
public bool CanUse;
#endregion
/// <summary>
/// Is this power cell currently drawing power every tick.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("enabled")]
public bool Enabled;
public bool Drawing;
/// <summary>
/// How much the entity draws while the UI is open.

View File

@@ -21,7 +21,7 @@ public abstract class SharedPowerCellSystem : EntitySystem
private void OnRejuventate(EntityUid uid, PowerCellSlotComponent component, RejuvenateEvent args)
{
if (!_itemSlots.TryGetSlot(uid, component.CellSlotId, out ItemSlot? itemSlot) || !itemSlot.Item.HasValue)
if (!_itemSlots.TryGetSlot(uid, component.CellSlotId, out var itemSlot) || !itemSlot.Item.HasValue)
return;
// charge entity batteries and remove booby traps.

View File

@@ -4,34 +4,42 @@ using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Projectiles
namespace Content.Shared.Projectiles;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ProjectileComponent : Component
{
[RegisterComponent, NetworkedComponent]
public sealed class ProjectileComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("impactEffect", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? ImpactEffect;
[ViewVariables(VVAccess.ReadWrite), DataField("impactEffect", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? ImpactEffect;
public EntityUid Shooter { get; set; }
/// <summary>
/// User that shot this projectile.
/// </summary>
[DataField("shooter"), AutoNetworkedField] public EntityUid Shooter;
public bool IgnoreShooter = true;
/// <summary>
/// Weapon used to shoot.
/// </summary>
[DataField("weapon"), AutoNetworkedField]
public EntityUid Weapon;
[DataField("damage", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = default!;
[DataField("ignoreShooter"), AutoNetworkedField]
public bool IgnoreShooter = true;
[DataField("deleteOnCollide")]
public bool DeleteOnCollide { get; } = true;
[DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = new();
[DataField("ignoreResistances")]
public bool IgnoreResistances { get; } = false;
[DataField("deleteOnCollide")]
public bool DeleteOnCollide = true;
// Get that juicy FPS hit sound
[DataField("soundHit")] public SoundSpecifier? SoundHit;
[DataField("ignoreResistances")]
public bool IgnoreResistances = false;
[DataField("soundForce")]
public bool ForceSound = false;
// Get that juicy FPS hit sound
[DataField("soundHit")] public SoundSpecifier? SoundHit;
public bool DamagedEntity;
}
[DataField("soundForce")]
public bool ForceSound = false;
public bool DamagedEntity;
}

View File

@@ -30,31 +30,18 @@ namespace Content.Shared.Projectiles
component.Shooter = uid;
Dirty(component);
}
}
[NetSerializable, Serializable]
public sealed class ProjectileComponentState : ComponentState
[Serializable, NetSerializable]
public sealed class ImpactEffectEvent : EntityEventArgs
{
public string Prototype;
public EntityCoordinates Coordinates;
public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
{
public ProjectileComponentState(EntityUid shooter, bool ignoreShooter)
{
Shooter = shooter;
IgnoreShooter = ignoreShooter;
}
public EntityUid Shooter { get; }
public bool IgnoreShooter { get; }
}
[Serializable, NetSerializable]
public sealed class ImpactEffectEvent : EntityEventArgs
{
public string Prototype;
public EntityCoordinates Coordinates;
public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
{
Prototype = prototype;
Coordinates = coordinates;
}
Prototype = prototype;
Coordinates = coordinates;
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Damage;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
namespace Content.Shared.Weapons.Marker;
/// <summary>
/// Marks an entity to take additional damage
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedDamageMarkerSystem))]
public sealed partial class DamageMarkerComponent : Component
{
/// <summary>
/// Sprite to apply to the entity while damagemarker is applied.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("effect")]
public SpriteSpecifier.Rsi? Effect = new(new ResPath("/Textures/Objects/Weapons/Effects"), "shield2");
/// <summary>
/// Sound to play when the damage marker is procced.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("sound")]
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/kinetic_accel.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("damage")]
public DamageSpecifier Damage = new();
/// <summary>
/// Entity that marked this entity for a damage surplus.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("marker"), AutoNetworkedField]
public EntityUid Marker;
[ViewVariables(VVAccess.ReadWrite), DataField("endTime", customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField]
public TimeSpan EndTime;
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Damage;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Marker;
/// <summary>
/// Applies <see cref="DamageMarkerComponent"/> when colliding with an entity.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedDamageMarkerSystem))]
public sealed partial class DamageMarkerOnCollideComponent : Component
{
[DataField("whitelist"), AutoNetworkedField]
public EntityWhitelist? Whitelist = new();
[ViewVariables(VVAccess.ReadWrite), DataField("duration"), AutoNetworkedField]
public TimeSpan Duration = TimeSpan.FromSeconds(5);
/// <summary>
/// Additional damage to be applied.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("damage")]
public DamageSpecifier Damage = new();
/// <summary>
/// How many more times we can apply it.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("amount"), AutoNetworkedField]
public int Amount = 1;
}

View File

@@ -0,0 +1,81 @@
using Content.Shared.Damage;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Shared.Weapons.Marker;
public abstract class SharedDamageMarkerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DamageMarkerOnCollideComponent, StartCollideEvent>(OnMarkerCollide);
SubscribeLocalEvent<DamageMarkerComponent, EntityUnpausedEvent>(OnMarkerUnpaused);
SubscribeLocalEvent<DamageMarkerComponent, AttackedEvent>(OnMarkerAttacked);
}
private void OnMarkerAttacked(EntityUid uid, DamageMarkerComponent component, AttackedEvent args)
{
if (component.Marker != args.Used)
return;
args.BonusDamage += component.Damage;
RemCompDeferred<DamageMarkerComponent>(uid);
_audio.PlayPredicted(component.Sound, uid, args.User);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<DamageMarkerComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.EndTime > _timing.CurTime)
continue;
RemCompDeferred<DamageMarkerComponent>(uid);
}
}
private void OnMarkerUnpaused(EntityUid uid, DamageMarkerComponent component, ref EntityUnpausedEvent args)
{
component.EndTime += args.PausedTime;
}
private void OnMarkerCollide(EntityUid uid, DamageMarkerOnCollideComponent component, ref StartCollideEvent args)
{
if (!args.OtherFixture.Hard ||
args.OurFixture.ID != SharedProjectileSystem.ProjectileFixture ||
component.Amount <= 0 ||
component.Whitelist?.IsValid(args.OtherEntity, EntityManager) == false ||
!TryComp<ProjectileComponent>(uid, out var projectile) ||
!projectile.Weapon.IsValid())
{
return;
}
// Markers are exclusive, deal with it.
var marker = EnsureComp<DamageMarkerComponent>(args.OtherEntity);
marker.Damage = new DamageSpecifier(component.Damage);
marker.Marker = projectile.Weapon;
marker.EndTime = _timing.CurTime + component.Duration;
component.Amount--;
Dirty(marker);
if (component.Amount <= 0)
{
QueueDel(uid);
}
else
{
Dirty(component);
}
}
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Melee.Components;
/// <summary>
/// Indicates that this meleeweapon requires wielding to be useable.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class MeleeRequiresWieldComponent : Component
{
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Damage;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
@@ -37,6 +38,8 @@ namespace Content.Shared.Weapons.Melee.Events
/// </summary>
public EntityCoordinates ClickLocation { get; }
public DamageSpecifier BonusDamage = new();
public AttackedEvent(EntityUid used, EntityUid user, EntityCoordinates clickLocation)
{
Used = used;

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.Weapons.Melee.Events;
/// <summary>
/// Raised directed on a weapon when attempt a melee attack.
/// </summary>
[ByRefEvent]
public record struct AttemptMeleeEvent(bool Cancelled, string? Message);

View File

@@ -15,6 +15,8 @@ using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Weapons.Melee.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
@@ -70,6 +72,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
SubscribeLocalEvent<MeleeWeaponComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<MeleeWeaponComponent, HandDeselectedEvent>(OnMeleeDropped);
SubscribeLocalEvent<MeleeWeaponComponent, HandSelectedEvent>(OnMeleeSelected);
SubscribeLocalEvent<MeleeWeaponComponent, GunShotEvent>(OnMeleeShot);
SubscribeAllEvent<HeavyAttackEvent>(OnHeavyAttack);
SubscribeAllEvent<LightAttackEvent>(OnLightAttack);
@@ -89,6 +92,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
#endif
}
private void OnMeleeShot(EntityUid uid, MeleeWeaponComponent component, ref GunShotEvent args)
{
if (!TryComp<GunComponent>(uid, out var gun))
return;
if (gun.NextFire > component.NextAttack)
{
component.NextAttack = gun.NextFire;
Dirty(component);
}
}
private void OnMeleeUnpaused(EntityUid uid, MeleeWeaponComponent component, ref EntityUnpausedEvent args)
{
component.NextAttack += args.PausedTime;
@@ -356,38 +371,64 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
}
// Windup time checked elsewhere.
var fireRate = TimeSpan.FromSeconds(1f / weapon.AttackRate);
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;
weapon.NextAttack += TimeSpan.FromSeconds(1f / weapon.AttackRate);
// Attack confirmed
string animation;
switch (attack)
while (weapon.NextAttack <= curTime)
{
case LightAttackEvent light:
DoLightAttack(user, light, weaponUid, weapon, session);
animation = weapon.ClickAnimation;
break;
case DisarmAttackEvent disarm:
if (!DoDisarm(user, disarm, weaponUid, weapon, session))
return;
animation = weapon.ClickAnimation;
break;
case HeavyAttackEvent heavy:
DoHeavyAttack(user, heavy, weaponUid, weapon, session);
animation = weapon.WideAnimation;
break;
default:
throw new NotImplementedException();
weapon.NextAttack += fireRate;
swings++;
}
DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation);
weapon.Attacking = true;
Dirty(weapon);
// Do this AFTER attack so it doesn't spam every tick
var ev = new AttemptMeleeEvent();
RaiseLocalEvent(weaponUid, ref ev);
if (ev.Cancelled)
{
if (ev.Message != null)
{
PopupSystem.PopupClient(ev.Message, weaponUid, user);
}
return;
}
// Attack confirmed
for (var i = 0; i < swings; i++)
{
string animation;
switch (attack)
{
case LightAttackEvent light:
DoLightAttack(user, light, weaponUid, weapon, session);
animation = weapon.ClickAnimation;
break;
case DisarmAttackEvent disarm:
if (!DoDisarm(user, disarm, weaponUid, weapon, session))
return;
animation = weapon.ClickAnimation;
break;
case HeavyAttackEvent heavy:
DoHeavyAttack(user, heavy, weaponUid, weapon, session);
animation = weapon.WideAnimation;
break;
default:
throw new NotImplementedException();
}
DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation);
}
weapon.Attacking = true;
}
/// <summary>
@@ -469,9 +510,10 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
Interaction.DoContactInteraction(user, ev.Target);
// For stuff that cares about it being attacked.
RaiseLocalEvent(ev.Target.Value, new AttackedEvent(meleeUid, user, targetXform.Coordinates));
var attackedEvent = new AttackedEvent(meleeUid, user, targetXform.Coordinates);
RaiseLocalEvent(ev.Target.Value, attackedEvent);
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList);
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
var damageResult = Damageable.TryChangeDamage(ev.Target, modifiedDamage, origin:user);
if (damageResult != null && damageResult.Total > FixedPoint2.Zero)
@@ -596,16 +638,15 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, light attacks.
Interaction.DoContactInteraction(user, target);
RaiseLocalEvent(target, new AttackedEvent(meleeUid, user, Transform(target).Coordinates));
}
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList);
var appliedDamage = new DamageSpecifier();
foreach (var entity in targets)
{
RaiseLocalEvent(entity, new AttackedEvent(meleeUid, user, ev.Coordinates));
var attackedEvent = new AttackedEvent(meleeUid, user, ev.Coordinates);
RaiseLocalEvent(entity, attackedEvent);
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
var damageResult = Damageable.TryChangeDamage(entity, modifiedDamage, origin:user);
@@ -631,7 +672,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (appliedDamage.Total > FixedPoint2.Zero)
{
var target = entities.First();
PlayHitSound(target, user, GetHighestDamageSound(modifiedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound);
PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound);
}
else
{

View File

@@ -79,6 +79,12 @@ public partial class GunComponent : Component
#endregion
/// <summary>
/// Whether this gun is shot via the use key or the alt-use key.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("useKey"), AutoNetworkedField]
public bool UseKey = true;
/// <summary>
/// Where the gun is being requested to shoot.
/// </summary>

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Ranged.Components;
/// <summary>
/// Indicates that this gun requires wielding to be useable.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class GunRequiresWieldComponent : Component
{
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.Timing;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Ranged.Components;
/// <summary>
/// Applies UseDelay whenever the entity shoots.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(UseDelayOnShootSystem))]
public sealed class UseDelayOnShootComponent : Component
{
}

View File

@@ -47,8 +47,10 @@ public sealed class RechargeBasicEntityAmmoSystem : EntitySystem
if (_gun.UpdateBasicEntityAmmoCount(uid, ammo.Count.Value + 1, ammo))
{
if (_netManager.IsClient && _timing.IsFirstTimePredicted)
_audio.Play(recharge.RechargeSound, Filter.Local(), uid, true);
// We don't predict this because occasionally on client it may not play.
// PlayPredicted will still be predicted on the client.
if (_netManager.IsServer)
_audio.PlayPvs(recharge.RechargeSound, uid);
}
if (ammo.Count == ammo.Capacity)

View File

@@ -13,11 +13,12 @@ using Content.Shared.Projectiles;
using Content.Shared.Tag;
using Content.Shared.Throwing;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
@@ -70,7 +71,7 @@ public abstract partial class SharedGunSystem : EntitySystem
Sawmill.Level = LogLevel.Info;
SubscribeAllEvent<RequestShootEvent>(OnShootRequest);
SubscribeAllEvent<RequestStopShootEvent>(OnStopShootRequest);
SubscribeLocalEvent<GunComponent, MeleeAttackAttemptEvent>(OnGunMeleeAttempt);
SubscribeLocalEvent<GunComponent, MeleeHitEvent>(OnGunMelee);
// Ammo providers
InitializeBallistic();
@@ -103,19 +104,23 @@ public abstract partial class SharedGunSystem : EntitySystem
#endif
}
private void OnGunMelee(EntityUid uid, GunComponent component, MeleeHitEvent args)
{
if (!TryComp<MeleeWeaponComponent>(uid, out var melee))
return;
if (melee.NextAttack > component.NextFire)
{
component.NextFire = melee.NextAttack;
Dirty(component);
}
}
private void OnGunUnpaused(EntityUid uid, GunComponent component, ref EntityUnpausedEvent args)
{
component.NextFire += args.PausedTime;
}
private void OnGunMeleeAttempt(EntityUid uid, GunComponent component, ref MeleeAttackAttemptEvent args)
{
if (TagSystem.HasTag(args.User, "GunsDisabled"))
return;
args.Cancelled = true;
}
private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args)
{
var user = args.SenderSession.AttachedEntity;
@@ -214,15 +219,12 @@ public abstract partial class SharedGunSystem : EntitySystem
if (toCoordinates == null)
return;
if (TagSystem.HasTag(user, "GunsDisabled"))
{
if (Timing.IsFirstTimePredicted)
Popup(Loc.GetString("gun-disabled"), user, user);
return;
}
var curTime = Timing.CurTime;
// Maybe Raise an event for this? CanAttack doesn't seem appropriate.
if (TryComp<MeleeWeaponComponent>(gunUid, out var melee) && melee.NextAttack > curTime)
return;
// Need to do this to play the clicking sound for empty automatic weapons
// but not play anything for burst fire.
if (gun.NextFire > curTime)
@@ -232,7 +234,8 @@ public abstract partial class SharedGunSystem : EntitySystem
// First shot
// Previously we checked shotcounter but in some cases all the bullets got dumped at once
if (gun.NextFire < curTime - fireRate)
// curTime - fireRate is insufficient because if you time it just right you can get a 3rd shot out slightly quicker.
if (gun.NextFire < curTime - fireRate || gun.ShotCounter == 0 && gun.NextFire < curTime)
gun.NextFire = curTime;
var shots = 0;
@@ -263,6 +266,20 @@ public abstract partial class SharedGunSystem : EntitySystem
throw new ArgumentOutOfRangeException($"No implemented shooting behavior for {gun.SelectedMode}!");
}
var attemptEv = new AttemptShootEvent(user, null);
RaiseLocalEvent(gunUid, ref attemptEv);
if (attemptEv.Cancelled)
{
if (attemptEv.Message != null)
{
PopupSystem.PopupClient(attemptEv.Message, gunUid, user);
}
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
return;
}
var fromCoordinates = Transform(user).Coordinates;
// Remove ammo
var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user);
@@ -279,10 +296,7 @@ public abstract partial class SharedGunSystem : EntitySystem
// where the gun may be SemiAuto or Burst.
gun.ShotCounter += shots;
var attemptEv = new AttemptShootEvent(user);
RaiseLocalEvent(gunUid, ref attemptEv);
if (ev.Ammo.Count <= 0 || attemptEv.Cancelled)
if (ev.Ammo.Count <= 0)
{
// Play empty gun sounds if relevant
// If they're firing an existing clip then don't play anything.
@@ -415,7 +429,7 @@ public abstract partial class SharedGunSystem : EntitySystem
/// <param name="Cancelled">Set this to true if the shot should be cancelled.</param>
/// <param name="ThrowItems">Set this to true if the ammo shouldn't actually be fired, just thrown.</param>
[ByRefEvent]
public record struct AttemptShootEvent(EntityUid User, bool Cancelled = false, bool ThrowItems = false);
public record struct AttemptShootEvent(EntityUid User, string? Message, bool Cancelled = false, bool ThrowItems = false);
/// <summary>
/// Raised directed on the gun after firing.

View File

@@ -0,0 +1,20 @@
using Content.Shared.Timing;
using Content.Shared.Weapons.Ranged.Components;
namespace Content.Shared.Weapons.Ranged.Systems;
public sealed class UseDelayOnShootSystem : EntitySystem
{
[Dependency] private readonly UseDelaySystem _delay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UseDelayOnShootComponent, GunShotEvent>(OnUseShoot);
}
private void OnUseShoot(EntityUid uid, UseDelayOnShootComponent component, ref GunShotEvent args)
{
_delay.BeginDelay(uid);
}
}

View File

@@ -8,6 +8,10 @@ using Content.Shared.Item;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Melee.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.Wieldable.Components;
using Robust.Shared.Player;
@@ -34,9 +38,32 @@ public sealed class WieldableSystem : EntitySystem
SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
SubscribeLocalEvent<WieldableComponent, DisarmAttemptEvent>(OnDisarmAttemptEvent);
SubscribeLocalEvent<MeleeRequiresWieldComponent, AttemptMeleeEvent>(OnMeleeAttempt);
SubscribeLocalEvent<GunRequiresWieldComponent, AttemptShootEvent>(OnShootAttempt);
SubscribeLocalEvent<IncreaseDamageOnWieldComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnMeleeAttempt(EntityUid uid, MeleeRequiresWieldComponent component, ref AttemptMeleeEvent args)
{
if (TryComp<WieldableComponent>(uid, out var wieldable) &&
!wieldable.Wielded)
{
args.Cancelled = true;
args.Message = Loc.GetString("wieldable-component-requires", ("item", uid));
}
}
private void OnShootAttempt(EntityUid uid, GunRequiresWieldComponent component, ref AttemptShootEvent args)
{
if (TryComp<WieldableComponent>(uid, out var wieldable) &&
!wieldable.Wielded)
{
args.Cancelled = true;
args.Message = Loc.GetString("wieldable-component-requires", ("item", uid));
}
}
private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args)
{
if (component.Wielded)

View File

@@ -0,0 +1,4 @@
- files: ["plasm_cutter.ogg"]
license: "CC-BY-SA-3.0"
copyright: "Taken from Citadel station."
source: "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/5b43cb2545a19957ec6ce3352dceac5e347e77df/sound/weapons/plasma_cutter.ogg"

Binary file not shown.

View File

@@ -9,9 +9,11 @@ wieldable-component-successful-wield-other = { THE($user) } wields { THE($item)
wieldable-component-failed-wield-other = { THE($user) } unwields { THE($item) }.
wieldable-component-no-hands = You don't have enough hands!
wieldable-component-not-enough-free-hands = {$number ->
wieldable-component-not-enough-free-hands = {$number ->
[one] You need a free hand to wield { THE($item) }.
*[other] You need { $number } free hands to wield { THE($item) }.
}
wieldable-component-not-in-hands = { CAPITALIZE(THE($item)) } isn't in your hands!
wieldable-component-requires = { CAPITALIZE(THE($item))} must be wielded!

View File

@@ -33,12 +33,6 @@
isLooped: true
property: Radius
enabled: false
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight }
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: ToggleableLightVisuals
spriteLayer: light
inhandVisuals:

View File

@@ -130,6 +130,7 @@
parent: BaseBulletHighVelocity
name: Icicle
description: Brrrrr.
noSpawn: true
components:
- type: Sprite
sprite: Structures/Specific/Anomalies/ice_anom.rsi

View File

@@ -309,6 +309,37 @@
- type: TimedDespawn
lifetime: 0.4
- type: entity
id: BulletCharge
name: charge bolt
parent: BaseBulletHighVelocity
noSpawn: true
description: Marks a target for additional damage.
components:
- type: Sprite
noRot: false
sprite: Objects/Weapons/Guns/Projectiles/magic.rsi
layers:
- state: chronobolt
shader: unshaded
- type: GatheringProjectile
- type: DamageMarkerOnCollide
whitelist:
components:
- MobState
damage:
types:
Blunt: 20
Slash: 5
- type: Projectile
impactEffect: BulletImpactEffectKinetic
damage:
types:
Blunt: 0
# Short lifespan
- type: TimedDespawn
lifetime: 0.4
- type: entity
parent: BaseBullet
id: AnomalousParticleDelta

View File

@@ -0,0 +1,96 @@
- type: entity
name: crusher
parent: BaseItem
id: BaseWeaponCrusher # Crusher? But I...
abstract: true
description: An early design of the proto-kinetic accelerator.
components:
- type: Sharp
- type: UnpoweredFlashlight
toggleAction:
name: action-name-toggle-light
description: action-description-toggle-light
icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight }
iconOn: Objects/Tools/flashlight.rsi/flashlight-on.png
event: !type:ToggleActionEvent
- type: PointLight
enabled: false
radius: 4
- type: entity
name: crusher
parent: BaseWeaponCrusher
id: WeaponCrusher
components:
- type: Sprite
sprite: Objects/Weapons/Melee/crusher.rsi
state: icon
- type: AmmoCounter
- type: UseDelayOnShoot
- type: UseDelay
delay: 1.9
- type: Gun
soundGunshot: /Audio/Weapons/plasma_cutter.ogg
fireRate: 0.5
useKey: false
- type: RechargeBasicEntityAmmo
rechargeCooldown: 1.5
rechargeSound:
path: /Audio/Weapons/Guns/MagIn/kinetic_reload.ogg
- type: BasicEntityAmmoProvider
proto: BulletCharge
capacity: 1
count: 1
- type: MeleeWeapon
attackRate: 0.75
damage:
types:
Blunt: 10
Slash: 5
- type: Wieldable
- type: MeleeRequiresWield
- type: GunRequiresWield
- type: Item
size: 150
- type: DisarmMalus
- type: Tool
qualities:
- Prying
- type: entity
name: crusher dagger
parent: BaseWeaponCrusher
id: WeaponCrusherDagger
description: A scaled down version of a proto-kinetic crusher, usually used in a last ditch scenario.
components:
- type: Sprite
sprite: Objects/Weapons/Melee/crusher_dagger.rsi
state: icon
- type: MeleeWeapon
attackRate: 1.5
damage:
types:
Slash: 6.5
- type: Item
size: 30
# Like a crusher... but better
- type: entity
name: crusher glaive
parent: WeaponCrusher
id: WeaponCrusherGlaive
description: An early design of the proto-kinetic accelerator, in glaive form.
components:
- type: UseDelayOnShoot
- type: UseDelay
delay: 1.9
- type: Gun
fireRate: 1
- type: RechargeBasicEntityAmmo
rechargeCooldown: 0.5
- type: Sprite
sprite: Objects/Weapons/Melee/crusher_glaive.rsi
- type: MeleeWeapon
attackRate: 1.25
- type: Item
size: 150

View File

@@ -3,6 +3,7 @@
description: Accelerated particles.
id: ParticlesProjectile
parent: BaseBullet
noSpawn: true
components:
- type: Sprite
layers:

View File

@@ -314,9 +314,6 @@
- type: Tag
id: GuideEmbeded
- type: Tag
id: GunsDisabled # Allow certain entities to not use guns without complicating the system with an event
- type: Tag
id: Handcuffs

View File

@@ -0,0 +1,25 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/tgstation/tgstation/blob/192e2ce0821c8ed347f3b4164e7d76fe344f4bbf/icons/effects/effects.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "shield2",
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -0,0 +1,42 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "icon-lit"
},
{
"name": "icon-uncharged",
"delays": [
[
0.3,
0.3
]
]
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

View File

@@ -0,0 +1,25 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "icon-lit"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

View File

@@ -0,0 +1,42 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/blob/817e7c1f225876b45891e3f06908e6d032f0a8bc/icons/obj/mining.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "icon-lit"
},
{
"name": "icon-uncharged",
"delays": [
[
0.3,
0.3
]
]
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B