diff --git a/Content.Shared/_Forge/FireModes/HybridAmmoProviderComponent.cs b/Content.Shared/_Forge/FireModes/HybridAmmoProviderComponent.cs new file mode 100644 index 000000000000..c6bd1d0bd6f3 --- /dev/null +++ b/Content.Shared/_Forge/FireModes/HybridAmmoProviderComponent.cs @@ -0,0 +1,45 @@ +using Content.Shared._Forge.TripleModeWeapon; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._Forge.FireModes +{ + + /// + /// Allows battery weapons to fire different types of projectiles + /// + [RegisterComponent, NetworkedComponent] + [Access(typeof(HybridModeWeaponSystem), typeof(SharedGunSystem))] + public sealed partial class HybridAmmoProviderComponent : AmmoProviderComponent + { + /// + /// How much battery it costs to fire once. + /// + [DataField("fireCost"), ViewVariables(VVAccess.ReadWrite)] + public float FireCost = 100; + + [ViewVariables(VVAccess.ReadWrite)] + public int Shots; + + [ViewVariables(VVAccess.ReadWrite)] + public int Capacity; + + [ViewVariables(VVAccess.ReadWrite), DataField("soundAutoEject")] + public SoundSpecifier? SoundAutoEject = new SoundPathSpecifier("/Audio/Weapons/Guns/EmptyAlarm/smg_empty_alarm.ogg"); + + /// + /// Should the magazine automatically eject when empty. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("autoEject")] + public bool AutoEject = false; + } +} diff --git a/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponComponent.cs b/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponComponent.cs new file mode 100644 index 000000000000..190fdd6d1646 --- /dev/null +++ b/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponComponent.cs @@ -0,0 +1,32 @@ +using Content.Shared._Forge.FireModes; +using Content.Shared.Access; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._Forge.TripleModeWeapon +{ + [RegisterComponent, NetworkedComponent] + [Access(typeof(HybridModeWeaponSystem))] + [AutoGenerateComponentState] + public sealed partial class HybridModeWeaponComponent: Component + { + [DataField("fireModes", required: true)] + [AutoNetworkedField] + public Dictionary, string> FireModes = default!; + + [DataField("proto", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Prototype = default!; + + [DataField] + [AutoNetworkedField] + public int CurrentFireMode; + } +} diff --git a/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponSystem.cs b/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponSystem.cs new file mode 100644 index 000000000000..d80bc9ba3a81 --- /dev/null +++ b/Content.Shared/_Forge/HybridModeWeapon/HybridModeWeaponSystem.cs @@ -0,0 +1,232 @@ +using Content.Shared._Forge.FireModes; +using Content.Shared.Access.Systems; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Content.Shared.Weapons.Ranged; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Toolshed.TypeParsers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Content.Shared._Forge.TripleModeWeapon +{ + + public sealed class HybridModeWeaponSystem : EntitySystem + { + protected const string ChamberSlot = "gun_chamber"; + + [Dependency] private readonly SharedGunSystem _sharedGunSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] protected readonly SharedContainerSystem _containerSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnGetVerb); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnAmmoCount); + + } + + private void OnAmmoCount(EntityUid uid, HybridAmmoProviderComponent component, ref GetAmmoCountEvent args) + { + args.Count = component.Shots; + args.Capacity = component.Capacity; + } + + private ProtoId GetMode(HybridModeWeaponComponent component) + { + return component.FireModes.ElementAt(component.CurrentFireMode).Key; + } + + + private void OnExamined(EntityUid uid, HybridModeWeaponComponent component, ExaminedEvent args) + { + if (component.FireModes.Count < 2) + return; + + var fireMode = GetMode(component); + + HitscanPrototype? hitscanPrototype = null; + + if (!_prototypeManager.TryIndex(fireMode, out var prototype) && !_prototypeManager.TryIndex(fireMode, out hitscanPrototype)) + return; + + if (prototype is not null) + args.PushMarkup(Loc.GetString("gun-set-fire-mode", ("mode", prototype.Name))); + + if (hitscanPrototype is not null) + args.PushMarkup("Лазер"); + } + + private void OnGetVerb(EntityUid uid, HybridModeWeaponComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract) + return; + + if (component.FireModes.Count < 2) + return; + + if (!_accessReaderSystem.IsAllowed(args.User, uid)) + return; + + for (var i = 0; i < component.FireModes.Count; i++) + { + var fireMode = component.FireModes.ElementAt(i); + EntityPrototype? entProto = null; + HitscanPrototype? hitProto = null; + if (_prototypeManager.TryIndex(fireMode.Key, out var prototype)) + { + entProto = prototype; + } + + if (_prototypeManager.TryIndex(fireMode.Key, out var hitscanPrototype)) + { + hitProto = hitscanPrototype; + } + var index = i; + + if (hitProto is null && entProto is null) + return; + + var v = new Verb + { + Priority = 1, + Category = VerbCategory.SelectType, + Text = entProto?.Name ?? "Лазер", + Disabled = i == component.CurrentFireMode, + Impact = LogImpact.Medium, + DoContactInteraction = true, + Act = () => + { + TrySetFireMode(uid, component, index, args.User); + } + }; + + args.Verbs.Add(v); + } + } + + private void OnUseInHand(EntityUid uid, HybridModeWeaponComponent component, ref UseInHandEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + TryCycleFireMode(uid, component, args.User); + } + + public void TryCycleFireMode(EntityUid uid, HybridModeWeaponComponent component, EntityUid? user = null) + { + if (component.FireModes.Count < 2) + return; + + var index = (component.CurrentFireMode + 1) % component.FireModes.Count; + TrySetFireMode(uid, component, index, user); + } + + public bool TrySetFireMode(EntityUid uid, HybridModeWeaponComponent component, int index, EntityUid? user = null) + { + if (index < 0 || index >= component.FireModes.Count) + return false; + + if (user != null && !_accessReaderSystem.IsAllowed(user.Value, uid)) + return false; + + SetFireMode(uid, component, index, user); + + return true; + } + + private void SetFireMode(EntityUid uid, HybridModeWeaponComponent component, int index, EntityUid? user = null) + { + component.CurrentFireMode = index; + var fireMode = component.FireModes.ElementAt(index); + var fireModeName = fireMode.Value; + EntityPrototype? prototype; + GunComponent? gunComponent = null; + + if (_prototypeManager.TryIndex(fireMode.Key, out prototype)) + { + if (TryComp(uid, out var appearance)) + _appearanceSystem.SetData(uid, BatteryWeaponFireModeVisuals.State, prototype.ID, appearance); + + if (user != null) + _popupSystem.PopupClient(Loc.GetString("gun-set-fire-mode", ("mode", prototype.Name)), uid, user.Value); + } + + if (_prototypeManager.TryIndex(fireMode.Key, out var hitscanPrototype)) + { + if (TryComp(uid, out var appearance)) + _appearanceSystem.SetData(uid, BatteryWeaponFireModeVisuals.State, hitscanPrototype.ID, appearance); + + if (user != null) + _popupSystem.PopupClient("Лазер", uid, user.Value); + } + + if (TryComp(uid, out var hitscanAmmoProvider)) + RemComp(uid); + + if (TryComp(uid, out var magazineAmmoProvider)) + RemComp(uid); + + + if (TryComp(uid, out var hybridAmmoProviderComponent)) + RemComp(uid); + + + switch (fireModeName) + { + case "battery": + if (hitscanPrototype is not null) + { + var hitscanComponent = (HitscanBatteryAmmoProviderComponent)_factory.GetComponent(typeof(HitscanBatteryAmmoProviderComponent)); + + hitscanComponent.NetSyncEnabled = false; + hitscanComponent.Prototype = hitscanPrototype.ID; + AddComp(uid, hitscanComponent); + Dirty(uid, hitscanComponent); + } + + break; + case "ammo": + AddComp(uid); + break; + case "hybrid": + AddComp(uid); + break; + } + + Dirty(uid, component); + + var updateClientAmmoEvent = new UpdateClientAmmoEvent(); + + RaiseLocalEvent(uid, ref updateClientAmmoEvent); + } + } +} diff --git a/Resources/Prototypes/_Forge/HybridWeapon/hybrid_weapon.yml b/Resources/Prototypes/_Forge/HybridWeapon/hybrid_weapon.yml new file mode 100644 index 000000000000..51f171c2bba8 --- /dev/null +++ b/Resources/Prototypes/_Forge/HybridWeapon/hybrid_weapon.yml @@ -0,0 +1,140 @@ +# - type: entity +# name: hybrid laser gun +# parent: [ WeaponAdvancedLaser, BaseXenoborgContraband ] +# id: HybridLaserGun +# components: +# - type: HybridAmmoProvider +# proto: NFRedHeavyLaser # Frontier: use NF variant + +- type: entity + name: hybrid laser gun + parent: [BaseWeaponBatterySmall, BaseC1Contraband] # Frontier: added BaseC1Contraband + id: HybridLaserGun + description: Better pray it won't burn your hands off. At least it's legal. + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Battery/makeshift.rsi + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - state: mag-unshaded-4 + map: ["enum.GunVisualLayers.MagUnshaded"] + shader: unshaded + - type: MagazineVisuals + magState: mag + steps: 5 + zeroVisible: false + - type: Appearance + - type: Clothing + sprite: Objects/Weapons/Guns/Battery/makeshift.rsi + - type: HybridAmmoProvider + proto: NFRedLightLaser # Frontier: use NF variant + fireCost: 62.5 + - type: HybridModeWeapon + proto: NFAnomalousParticleDeltaStrong + fireModes: + NFPulse: battery + WeaponRifleAkHybrid: ammo + HybridLaserGun: hybrid + - type: ItemSlots + slots: + gun_magazine: + name: Magazine + # startingItem: MagazineLightRifle # Frontier + insertSound: /Audio/Weapons/Guns/MagIn/ltrifle_magin.ogg + ejectSound: /Audio/Weapons/Guns/MagOut/ltrifle_magout.ogg + priority: 2 + whitelist: + tags: + - MagazineLightRifle + - type: ContainerContainer + containers: + gun_magazine: !type:ContainerSlot + + +- type: entity + categories: [ HideSpawnMenu ] # Frontier + name: AKMS + parent: [BaseWeaponRifle] # Frontier: BaseSecurityContraband