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;
+ private bool _markersVisible;
- public bool MarkersVisible
+ public bool MarkersVisible
+ {
+ get => _markersVisible;
+ set
{
- get => _markersVisible;
- set
- {
- _markersVisible = value;
- UpdateMarkers();
- }
+ _markersVisible = value;
+ UpdateMarkers();
}
+ }
- public override void Initialize()
- {
- base.Initialize();
+ public override void Initialize()
+ {
+ base.Initialize();
- SubscribeLocalEvent<MarkerComponent, ComponentStartup>(OnStartup);
- }
+ SubscribeLocalEvent<MarkerComponent, ComponentStartup>(OnStartup);
+ }
- private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args)
- {
- UpdateVisibility(marker);
- }
+ private void OnStartup(EntityUid uid, MarkerComponent marker, ComponentStartup args)
+ {
+ UpdateVisibility(uid);
+ }
- private void UpdateVisibility(MarkerComponent marker)
+ private void UpdateVisibility(EntityUid uid)
+ {
+ if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
{
- if (EntityManager.TryGetComponent(marker.Owner, out SpriteComponent? sprite))
- {
- sprite.Visible = MarkersVisible;
- }
+ sprite.Visible = MarkersVisible;
}
+ }
+
+ private void UpdateMarkers()
+ {
+ var query = AllEntityQuery<MarkerComponent>();
- private void UpdateMarkers()
+ while (query.MoveNext(out var uid, out var comp))
{
- foreach (var markerComponent in EntityManager.EntityQuery<MarkerComponent>(true))
- {
- UpdateVisibility(markerComponent);
- }
+ UpdateVisibility(uid);
}
}
}
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<ProjectileComponent, ComponentHandleState>(OnHandleState);
SubscribeNetworkEvent<ImpactEffectEvent>(OnProjectileImpact);
}
_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;
- }
}
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";
--- /dev/null
+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
+ }
+}
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;
// 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)
{
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;
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 });
_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");
comp.Damage *= severity;
- _gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, component.MaxProjectileSpeed * severity);
+ _gunSystem.ShootProjectile(ent, direction, Vector2.Zero, uid, uid, component.MaxProjectileSpeed * severity);
}
}
[RegisterComponent]
public sealed class GatheringProjectileComponent : Component
{
-
+ /// <summary>
+ /// How many more times we can gather.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite), DataField("amount")]
+ public int Amount = 1;
}
{
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);
}
}
-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;
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;
{
base.Initialize();
- SubscribeLocalEvent<HandheldLightComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<HandheldLightComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<HandheldLightComponent, ExaminedEvent>(OnExamine);
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)
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)
: 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)
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;
--- /dev/null
+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);
+ }
+ }
+}
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!;
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);
- }
+ SubscribeLocalEvent<BatteryComponent, BeingMicrowavedEvent>(OnMicrowaved);
}
private void OnRejuvenate(EntityUid uid, PowerCellComponent component, RejuvenateEvent args)
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)
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));
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))
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;
}
{
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)
using Content.Server.PowerCell;
+using Content.Shared.PowerCell;
using Content.Shared.UserInterface;
namespace Content.Server.UserInterface;
--- /dev/null
+using Content.Shared.Weapons.Marker;
+
+namespace Content.Server.Weapons;
+
+public sealed class DamageMarkerSystem : SharedDamageMarkerSystem
+{
+
+}
// 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;
}
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);
}
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:
});
}
- 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))
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);
{
var projectile = EnsureComp<ProjectileComponent>(uid);
Projectiles.SetShooter(projectile, user.Value);
+ projectile.Weapon = gunUid;
}
TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle());
+++ /dev/null
-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;
- }
-}
+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.
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.
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;
+
+ /// <summary>
+ /// User that shot this projectile.
+ /// </summary>
+ [DataField("shooter"), AutoNetworkedField] public EntityUid Shooter;
- public EntityUid Shooter { get; set; }
+ /// <summary>
+ /// Weapon used to shoot.
+ /// </summary>
+ [DataField("weapon"), AutoNetworkedField]
+ public EntityUid Weapon;
- public bool IgnoreShooter = true;
+ [DataField("ignoreShooter"), AutoNetworkedField]
+ public bool IgnoreShooter = true;
- [DataField("damage", required: true)]
- [ViewVariables(VVAccess.ReadWrite)]
- public DamageSpecifier Damage = default!;
+ [DataField("damage", required: true)] [ViewVariables(VVAccess.ReadWrite)]
+ public DamageSpecifier Damage = new();
- [DataField("deleteOnCollide")]
- public bool DeleteOnCollide { get; } = true;
+ [DataField("deleteOnCollide")]
+ public bool DeleteOnCollide = true;
- [DataField("ignoreResistances")]
- public bool IgnoreResistances { get; } = false;
+ [DataField("ignoreResistances")]
+ public bool IgnoreResistances = false;
- // Get that juicy FPS hit sound
- [DataField("soundHit")] public SoundSpecifier? SoundHit;
+ // Get that juicy FPS hit sound
+ [DataField("soundHit")] public SoundSpecifier? SoundHit;
- [DataField("soundForce")]
- public bool ForceSound = false;
+ [DataField("soundForce")]
+ public bool ForceSound = false;
- public bool DamagedEntity;
- }
+ public bool DamagedEntity;
}
component.Shooter = uid;
Dirty(component);
}
+ }
- [NetSerializable, Serializable]
- public sealed class ProjectileComponentState : ComponentState
- {
- 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;
- [Serializable, NetSerializable]
- public sealed class ImpactEffectEvent : EntityEventArgs
+ public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
{
- public string Prototype;
- public EntityCoordinates Coordinates;
-
- public ImpactEffectEvent(string prototype, EntityCoordinates coordinates)
- {
- Prototype = prototype;
- Coordinates = coordinates;
- }
+ Prototype = prototype;
+ Coordinates = coordinates;
}
}
}
--- /dev/null
+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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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
+{
+
+}
+using Content.Shared.Damage;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
/// </summary>
public EntityCoordinates ClickLocation { get; }
+ public DamageSpecifier BonusDamage = new();
+
public AttackedEvent(EntityUid used, EntityUid user, EntityCoordinates clickLocation)
{
Used = used;
--- /dev/null
+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);
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;
SubscribeLocalEvent<MeleeWeaponComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<MeleeWeaponComponent, HandDeselectedEvent>(OnMeleeDropped);
SubscribeLocalEvent<MeleeWeaponComponent, HandSelectedEvent>(OnMeleeSelected);
+ SubscribeLocalEvent<MeleeWeaponComponent, GunShotEvent>(OnMeleeShot);
SubscribeAllEvent<HeavyAttackEvent>(OnHeavyAttack);
SubscribeAllEvent<LightAttackEvent>(OnLightAttack);
#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;
}
// 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);
+ while (weapon.NextAttack <= curTime)
+ {
+ weapon.NextAttack += fireRate;
+ swings++;
+ }
- // Attack confirmed
- string animation;
+ Dirty(weapon);
- switch (attack)
+ // Do this AFTER attack so it doesn't spam every tick
+ var ev = new AttemptMeleeEvent();
+ RaiseLocalEvent(weaponUid, ref ev);
+
+ if (ev.Cancelled)
{
- case LightAttackEvent light:
- DoLightAttack(user, light, weaponUid, weapon, session);
- animation = weapon.ClickAnimation;
- break;
- case DisarmAttackEvent disarm:
- if (!DoDisarm(user, disarm, weaponUid, weapon, session))
- return;
+ if (ev.Message != null)
+ {
+ PopupSystem.PopupClient(ev.Message, weaponUid, user);
+ }
- animation = weapon.ClickAnimation;
- break;
- case HeavyAttackEvent heavy:
- DoHeavyAttack(user, heavy, weaponUid, weapon, session);
- animation = weapon.WideAnimation;
- break;
- default:
- throw new NotImplementedException();
+ 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);
}
- DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation);
weapon.Attacking = true;
- Dirty(weapon);
}
/// <summary>
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)
// 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);
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
{
#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>
--- /dev/null
+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
+{
+
+}
--- /dev/null
+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
+{
+
+}
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)
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;
Sawmill.Level = LogLevel.Info;
SubscribeAllEvent<RequestShootEvent>(OnShootRequest);
SubscribeAllEvent<RequestStopShootEvent>(OnStopShootRequest);
- SubscribeLocalEvent<GunComponent, MeleeAttackAttemptEvent>(OnGunMeleeAttempt);
+ SubscribeLocalEvent<GunComponent, MeleeHitEvent>(OnGunMelee);
// Ammo providers
InitializeBallistic();
#endif
}
- private void OnGunUnpaused(EntityUid uid, GunComponent component, ref EntityUnpausedEvent args)
+ private void OnGunMelee(EntityUid uid, GunComponent component, MeleeHitEvent args)
{
- component.NextFire += args.PausedTime;
+ if (!TryComp<MeleeWeaponComponent>(uid, out var melee))
+ return;
+
+ if (melee.NextAttack > component.NextFire)
+ {
+ component.NextFire = melee.NextAttack;
+ Dirty(component);
+ }
}
- private void OnGunMeleeAttempt(EntityUid uid, GunComponent component, ref MeleeAttackAttemptEvent args)
+ private void OnGunUnpaused(EntityUid uid, GunComponent component, ref EntityUnpausedEvent args)
{
- if (TagSystem.HasTag(args.User, "GunsDisabled"))
- return;
-
- args.Cancelled = true;
+ component.NextFire += args.PausedTime;
}
private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args)
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)
// 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;
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);
// 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.
/// <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.
--- /dev/null
+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);
+ }
+}
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;
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)
--- /dev/null
+- 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"
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!
+
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:
parent: BaseBulletHighVelocity
name: Icicle
description: Brrrrr.
+ noSpawn: true
components:
- type: Sprite
sprite: Structures/Specific/Anomalies/ice_anom.rsi
- 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
--- /dev/null
+- 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
description: Accelerated particles.
id: ParticlesProjectile
parent: BaseBullet
+ noSpawn: true
components:
- type: Sprite
layers:
- 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
--- /dev/null
+{
+ "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
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+ }
+ ]
+}
\ No newline at end of file