Add flamethrower (#253)

* Add flamethrower

* Fix RSI

* Add GasAmmoProvider

* Add hidden crafting

* Remove DoubleSwordCraft

* Fix yml
This commit is contained in:
Aviu00
2023-08-14 15:55:12 +03:00
committed by Aviu00
parent f585cbb5cc
commit 93be8a0c97
28 changed files with 648 additions and 69 deletions

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Construction.Components;
using Content.Server.Containers;
using Content.Shared.Construction;
@@ -5,6 +6,8 @@ using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Content.Shared.Containers;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Item;
using Robust.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
@@ -394,6 +397,16 @@ namespace Content.Server.Construction
}
}
if (userUid != null && IsTransformParentOf(userUid.Value, transform) &&
TryComp(userUid, out HandsComponent? hands))
{
var hand = hands.Hands.Values.FirstOrDefault(h => h.HeldEntity == uid);
if (hand != null)
_handsSystem.TryDrop(userUid.Value, hand, handsComp: hands);
_handsSystem.PickupOrDrop(userUid, newUid, handsComp: hands);
}
var entChangeEv = new ConstructionChangeEntityEvent(newUid, uid);
RaiseLocalEvent(uid, entChangeEv);
RaiseLocalEvent(newUid, entChangeEv, broadcast: true);
@@ -410,6 +423,14 @@ namespace Content.Server.Construction
return newUid;
}
bool IsTransformParentOf(EntityUid uid, TransformComponent target) // WD
{
var parentUid = target.ParentUid;
return parentUid == uid ||
TryComp(parentUid, out TransformComponent? trans) && IsTransformParentOf(uid, trans);
}
/// <summary>
/// Performs a construction graph change on a construction entity, also changing the node to a valid one on
/// the new graph.

View File

@@ -0,0 +1,6 @@
namespace Content.Server.White.Flamethrower;
[RegisterComponent]
public sealed partial class FlamethrowerComponent : Component
{
}

View File

@@ -0,0 +1,27 @@
using Content.Server.IgnitionSource;
using Content.Shared.Temperature;
using Content.Shared.Weapons.Ranged.Systems;
namespace Content.Server.White.Flamethrower;
public sealed class FlamethrowerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FlamethrowerComponent, GunShotEvent>(OnShoot);
}
private void OnShoot(EntityUid uid, FlamethrowerComponent component, ref GunShotEvent args)
{
var hasIgnition = TryComp(uid, out IgnitionSourceComponent? ignition);
var isHotEvent = new IsHotEvent {IsHot = hasIgnition && ignition!.Ignited};
foreach (var (shotUid, _) in args.Ammo)
{
if(shotUid is not null)
RaiseLocalEvent(shotUid.Value, isHotEvent);
}
}
}

View File

@@ -0,0 +1,19 @@
using Content.Server.Atmos;
namespace Content.Server.White.Flamethrower;
[RegisterComponent]
public sealed partial class GasProjectileComponent : Component
{
public GasMixture? GasMixture;
public TileInfo? LastTile;
public TileInfo? CurTile;
[DataField("gasUsagePerTile", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public float GasUsagePerTile;
}
public record struct TileInfo(EntityUid? GridUid, EntityUid? MapUid, Vector2i Tile);

View File

@@ -0,0 +1,78 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.IgnitionSource;
using Robust.Server.GameObjects;
using Robust.Shared.Physics.Events;
namespace Content.Server.White.Flamethrower;
public sealed class GasProjectileSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GasProjectileComponent, StartCollideEvent>(OnCollide);
}
private void OnCollide(EntityUid uid, GasProjectileComponent component, ref StartCollideEvent args)
{
if (component.GasMixture is null)
return;
var tileInfo = HasComp<AirtightComponent>(args.OtherEntity) ? component.LastTile : component.CurTile;
if (tileInfo is null)
return;
var tile = tileInfo.Value;
var environment = _atmos.GetTileMixture(tile.GridUid, tile.MapUid, tile.Tile, true);
if (environment is null)
return;
_atmos.Merge(environment, component.GasMixture);
if (tile.GridUid is null || !TryComp(uid, out IgnitionSourceComponent? ignition) || !ignition.Ignited)
return;
_atmos.HotspotExpose(tile.GridUid.Value, tile.Tile, ignition.Temperature, 50, uid, true);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityManager.EntityQueryEnumerator<GasProjectileComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var proj, out var trans))
{
if (proj.GasMixture is null || proj.GasMixture.TotalMoles == 0f)
{
QueueDel(uid);
continue;
}
var tileCoords = _transformSystem.GetGridOrMapTilePosition(uid, trans);
var tileInfo = new TileInfo(trans.GridUid, trans.MapUid, tileCoords);
if (proj.CurTile == tileInfo)
continue;
proj.LastTile = proj.CurTile;
proj.CurTile = tileInfo;
var environment = _atmos.GetTileMixture(trans.GridUid, trans.MapUid, tileCoords, true);
var removed = proj.GasMixture.Remove(proj.GasUsagePerTile);
if (environment is null)
continue;
_atmos.Merge(environment, removed);
}
}
}

View File

@@ -0,0 +1,110 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.White.Flamethrower;
using Content.Shared.White.Flamethrower;
using Robust.Shared.Containers;
namespace Content.Server.Weapons.Ranged.Systems;
public sealed partial class GunSystem
{
[Dependency] private readonly GasTankSystem _gasTank = default!;
private const string GasTankSlot = "gas_tank";
protected override void InitializeGas()
{
base.InitializeGas();
SubscribeLocalEvent<GasAmmoProviderComponent, ComponentStartup>(OnGasStartup);
SubscribeLocalEvent<GasAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnGasSlotChange);
SubscribeLocalEvent<GasAmmoProviderComponent, EntRemovedFromContainerMessage>(OnGasSlotChange);
}
private void OnGasSlotChange(EntityUid uid, GasAmmoProviderComponent component, ContainerModifiedMessage args)
{
if (GasTankSlot != args.Container.ID)
return;
UpdateShots(uid, component);
}
private void OnGasStartup(EntityUid uid, GasAmmoProviderComponent component, ComponentStartup args)
{
UpdateShots(uid, component);
}
private static int CalculateShots(GasAmmoProviderComponent component, GasTankComponent tank)
{
return (int) MathF.Ceiling(tank.Air.TotalMoles / component.GasUsage);
}
private void UpdateShots(EntityUid uid, GasAmmoProviderComponent component)
{
var shots = 0;
var pressure = 0f;
if (TryTakeGasTankComponent(uid, out var tank, out _))
{
shots = CalculateShots(component, tank);
pressure = tank.Air.Pressure;
}
UpdateShots(component, shots, pressure);
}
private void UpdateShots(GasAmmoProviderComponent component, int shots, float pressure)
{
if (component.Shots != shots || MathF.Abs(component.Pressure - pressure) > 1e-3f)
{
Dirty(component);
}
component.Shots = shots;
component.Pressure = pressure;
}
private bool TryTakeGasTankComponent(EntityUid uid, [NotNullWhen(true)] out GasTankComponent? tank,
[NotNullWhen(true)] out EntityUid? tankUid)
{
if (!Containers.TryGetContainer(uid, GasTankSlot, out var container) ||
container is not ContainerSlot slot)
{
tank = null;
tankUid = null;
return false;
}
tankUid = slot.ContainedEntity;
if (tankUid != null)
return TryComp(tankUid, out tank);
tank = null;
return false;
}
protected override void InitShot(EntityUid uid, GasAmmoProviderComponent component, EntityUid shotUid)
{
if (!TryTakeGasTankComponent(uid, out var tank, out var tankUid) ||
!TryComp(shotUid, out GasProjectileComponent? proj))
return;
var trans = Transform(uid);
var curTile = TransformSystem.GetGridOrMapTilePosition(uid, trans);
var tileInfo = new TileInfo(trans.GridUid, trans.MapUid, curTile);
proj.LastTile = tileInfo;
proj.CurTile = tileInfo;
var removed = _gasTank.RemoveAir((tankUid.Value, tank), component.GasUsage);
if(removed is not null)
proj.GasMixture = removed;
UpdateShots(component, CalculateShots(component, tank), tank.Air.Pressure);
}
}

View File

@@ -1,57 +0,0 @@
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Robust.Shared.Audio;
namespace Content.Server.White.Other.EnergySword;
public sealed class EnergyDoubleSwordCraftSystem : EntitySystem
{
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoubleSwordCraftComponent, InteractUsingEvent>(Combine);
}
private const string NeededEnt = "EnergySword";
private const string EnergyDoubleSword = "EnergyDoubleSword";
private void Combine(EntityUid uid, DoubleSwordCraftComponent component, InteractUsingEvent args)
{
if (args.Handled)
return;
var user = args.User;
var usedEnt = _entityManager.GetComponent<MetaDataComponent>(args.Used).EntityPrototype!.ID;
var usedTo = _entityManager.GetComponent<MetaDataComponent>(uid).EntityPrototype!.ID;
if (usedTo is EnergyDoubleSword)
return;
if (usedEnt != NeededEnt || usedTo != NeededEnt)
return;
DeleteUsed(args.Used, uid);
SpawnEnergyDoubleSword(user);
}
private void DeleteUsed(EntityUid itemA, EntityUid itemB)
{
_entityManager.DeleteEntity(itemA);
_entityManager.DeleteEntity(itemB);
}
private void SpawnEnergyDoubleSword(EntityUid player)
{
var transform = CompOrNull<TransformComponent>(player)?.Coordinates;
if (transform == null)
return;
var weaponEntity = _entityManager.SpawnEntity(EnergyDoubleSword, transform.Value);
_handsSystem.PickupOrDrop(player, weaponEntity);
}
}

View File

@@ -1,6 +0,0 @@
namespace Content.Server.White.Other.EnergySword;
[RegisterComponent]
public sealed partial class DoubleSwordCraftComponent : Component
{
}

View File

@@ -86,6 +86,7 @@ public abstract partial class SharedGunSystem : EntitySystem
InitializeClothing();
InitializeContainer();
InitializeSolution();
InitializeGas(); // WD
// Interactions
SubscribeLocalEvent<GunComponent, GetVerbsEvent<AlternativeVerb>>(OnAltVerb);

View File

@@ -0,0 +1,31 @@
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.White.Flamethrower;
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
public sealed partial class GasAmmoProviderComponent : AmmoProviderComponent
{
[DataField("gasUsage", required: true)]
[AutoNetworkedField]
public float GasUsage;
[ViewVariables]
[AutoNetworkedField]
public int Shots;
[ViewVariables]
[AutoNetworkedField]
public int Capacity;
[ViewVariables]
[AutoNetworkedField]
public float Pressure;
[ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
[AutoNetworkedField]
public string Prototype = default!;
}

View File

@@ -0,0 +1,44 @@
using Content.Shared.Examine;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.White.Flamethrower;
namespace Content.Shared.Weapons.Ranged.Systems;
public abstract partial class SharedGunSystem
{
protected virtual void InitializeGas()
{
SubscribeLocalEvent<GasAmmoProviderComponent, TakeAmmoEvent>(OnGasTakeAmmo);
SubscribeLocalEvent<GasAmmoProviderComponent, GetAmmoCountEvent>(OnGasAmmoCount);
SubscribeLocalEvent<GasAmmoProviderComponent, ExaminedEvent>(OnGasExamine);
}
private void OnGasExamine(EntityUid uid, GasAmmoProviderComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("comp-gas-tank-examine", ("pressure", MathF.Round(component.Pressure))));
}
private void OnGasAmmoCount(EntityUid uid, GasAmmoProviderComponent component, ref GetAmmoCountEvent args)
{
args.Count = component.Shots;
args.Capacity = component.Capacity;
}
private void OnGasTakeAmmo(EntityUid uid, GasAmmoProviderComponent component, TakeAmmoEvent args)
{
var shots = Math.Min(args.Shots, component.Shots);
if (shots == 0)
return;
for (var i = 0; i < shots; i++)
{
var ent = Spawn(component.Prototype, args.Coordinates);
InitShot(uid, component, ent);
args.Ammo.Add((ent, EnsureComp<AmmoComponent>(ent)));
}
}
protected virtual void InitShot(EntityUid uid, GasAmmoProviderComponent component, EntityUid shotUid) {}
}

View File

@@ -116,7 +116,7 @@
- type: entity
name: cowelding tool
parent: Welder
parent: BaseWelder
id: Cowelder
description: "Melts anything as long as it's fueled, don't forget your eye protection! Moo!"
components:

View File

@@ -1,4 +1,4 @@
- type: entity
- type: entity
abstract: true
parent: BaseItem
id: GasTankBase
@@ -250,3 +250,6 @@
outputPressure: 101.3
- type: Clothing
sprite: Objects/Tanks/plasma.rsi
- type: Tag
tags:
- TankPlasma

View File

@@ -1,8 +1,9 @@
- type: entity
name: welding tool
parent: BaseItem
id: Welder
id: BaseWelder
description: "Melts anything as long as it's fueled, don't forget your eye protection!"
noSpawn: true
components:
- type: EmitSoundOnLand
sound:
@@ -99,9 +100,20 @@
- type: IgnitionSource
temperature: 700
- type: entity
name: welding tool
parent: BaseWelder
id: Welder
description: "Melts anything as long as it's fueled, don't forget your eye protection!"
components:
- type: Construction
deconstructionTarget: null
graph: WeaponFlamethrowerGraph
node: welder
- type: entity
name: industrial welding tool
parent: Welder
parent: BaseWelder
id: WelderIndustrial
description: "An industrial welder with over double the fuel capacity."
components:
@@ -139,7 +151,7 @@
- type: entity
name: experimental welding tool
parent: Welder
parent: BaseWelder
id: WelderExperimental
description: "An experimental welder capable of self-fuel generation and less harmful to the eyes."
components:
@@ -167,7 +179,7 @@
- type: entity
name: emergency welding tool
parent: Welder
parent: BaseWelder
id: WelderMini
description: "A miniature welder used during emergencies."
components:

View File

@@ -78,6 +78,13 @@
malus: 0
- type: Reflect
enabled: false
- type: Tag
tags:
- EnergySword
- type: Construction
deconstructionTarget: null
graph: EnergyDoubleSwordGraph
node: esword
- type: entity
name: pen
@@ -244,3 +251,7 @@
energeticChance: 0.6
kineticChance: 0.6
spread: 45
- type: Construction
deconstructionTarget: null
graph: EnergyDoubleSwordGraph
node: desword

View File

@@ -0,0 +1,32 @@
- type: entity
id: ProjectileFlamethrower
name: ProjectileFlamethrower
description: ProjectileFlamethrower
noSpawn: true
components:
- type: GasProjectile
gasUsagePerTile: 0.7
- type: Projectile
damage:
types:
Structural: 0
- type: TimedDespawn
lifetime: 0.2
- type: Physics
bodyType: Dynamic
linearDamping: 0
angularDamping: 0
- type: Fixtures
fixtures:
projectile:
shape:
!type:PhysShapeAabb
bounds: "-0.1,-0.1,0.1,0.1"
hard: false
mask:
- Impassable
- BulletImpassable
- type: IgnitionSource
temperature: 4000
- type: Ammo
muzzleFlash: null

View File

@@ -0,0 +1,105 @@
- type: entity
name: огнемёт
parent: BaseWelder
id: WeaponFlamethrower
description: Отлично подходит для сжигания фурри.
components:
- type: Sprite
sprite: White/Objects/Weapons/flamethrower.rsi
layers:
- state: icon
- state: tank
map: [ "tank" ]
visible: false
- state: flame
shader: unshaded
visible: false
map: ["enum.ToggleVisuals.Layer"]
- type: Item
size: Large
sprite: White/Objects/Weapons/flamethrower.rsi
- type: Clothing
quickEquip: false
slots:
- Back
- type: Gun
cameraRecoilScalar: 0
fireRate: 2
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/water_spray.ogg
- type: GasAmmoProvider
gasUsage: 2.1
proto: ProjectileFlamethrower
- type: Flamethrower
- type: ItemMapper
containerWhitelist: [gas_tank]
mapLayers:
tank:
whitelist:
tags:
- TankPlasma
- type: ItemSlots
slots:
gas_tank:
name: Баллон с плазмой
whitelist:
tags:
- TankPlasma
insertSound:
path: /Audio/Weapons/click.ogg
params:
volume: -3
- type: ContainerContainer
containers:
gas_tank: !type:ContainerSlot
- type: ToggleableLightVisuals
spriteLayer: flame
inhandVisuals:
left:
- state: inhand-left-flame
shader: unshaded
right:
- state: inhand-right-flame
shader: unshaded
- type: Construction
deconstructionTarget: null
graph: WeaponFlamethrowerGraph
node: flamethrower
- type: entity
name: часть огнемёта
parent: BaseItem
id: WeaponFlamethrowerNoIgniter
description: Недоделанный огнемёт.
components:
- type: Sprite
sprite: White/Objects/Weapons/flamethrower.rsi
layers:
- state: no-igniter
- type: Item
size: Large
sprite: White/Objects/Weapons/flamethrower.rsi
- type: Clothing
quickEquip: false
slots:
- Back
- type: Construction
deconstructionTarget: null
graph: WeaponFlamethrowerGraph
node: no-igniter
- type: entity
name: часть огнемёта
parent: WeaponFlamethrowerNoIgniter
id: WeaponFlamethrowerUnscrewed
description: Недоделанный огнемёт.
components:
- type: Sprite
sprite: White/Objects/Weapons/flamethrower.rsi
layers:
- state: icon
- type: Construction
deconstructionTarget: null
graph: WeaponFlamethrowerGraph
node: unscrewed

View File

@@ -0,0 +1,46 @@
- type: constructionGraph
id: EnergyDoubleSwordGraph
start: esword
graph:
- node: esword
entity: EnergySword
edges:
- to: desword
steps:
- tag: EnergySword
- node: desword
entity: EnergyDoubleSword
- type: constructionGraph
id: WeaponFlamethrowerGraph
start: welder
graph:
- node: welder
entity: Welder
edges:
- to: screwed-welder
steps:
- tool: Screwing
doAfter: 1
- node: screwed-welder
edges:
- to: no-igniter
steps:
- material: MetalRod
amount: 5
doAfter: 5
- node: no-igniter
entity: WeaponFlamethrowerNoIgniter
edges:
- to: unscrewed
steps:
- tag: Igniter
- node: unscrewed
entity: WeaponFlamethrowerUnscrewed
edges:
- to: flamethrower
steps:
- tool: Screwing
doAfter: 1
- node: flamethrower
entity: WeaponFlamethrower

View File

@@ -0,0 +1,5 @@
- type: Tag
id: TankPlasma
- type: Tag
id: EnergySword

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1,91 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation at bf8d2971568ead3cca52bee775101fd2cc26d9a7",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "no-igniter"
},
{
"name": "tank"
},
{
"name": "flame",
"delays": [
[
0.2,
0.2,
0.2,
0.2
]
]
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-left-flame",
"directions": 4,
"delays": [
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
]
]
},
{
"name": "inhand-right-flame",
"directions": 4,
"delays": [
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B