Merge branch 'master' into pr/39875

This commit is contained in:
slarticodefast
2025-10-18 19:25:06 +02:00
78 changed files with 1139 additions and 476 deletions

View File

@@ -5,6 +5,8 @@ using Content.Client.Items;
using Content.Client.Weapons.Ranged.Components; using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
using Content.Shared.Damage;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
@@ -16,6 +18,7 @@ using Robust.Client.Input;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Client.State; using Robust.Client.State;
using Robust.Shared.Animations; using Robust.Shared.Animations;
using Robust.Shared.Audio;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
@@ -234,6 +237,7 @@ public sealed partial class GunSystem : SharedGunSystem
continue; continue;
} }
// TODO: Clean this up in a gun refactor at some point - too much copy pasting
switch (shootable) switch (shootable)
{ {
case CartridgeAmmoComponent cartridge: case CartridgeAmmoComponent cartridge:
@@ -266,7 +270,7 @@ public sealed partial class GunSystem : SharedGunSystem
else else
RemoveShootable(ent.Value); RemoveShootable(ent.Value);
break; break;
case HitscanPrototype: case HitscanAmmoComponent:
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
Recoil(user, direction, gun.CameraRecoilScalarModified); Recoil(user, direction, gun.CameraRecoilScalarModified);
break; break;
@@ -404,4 +408,7 @@ public sealed partial class GunSystem : SharedGunSystem
_animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light"); _animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light");
_animPlayer.Play((gunUid, uidPlayer), animTwo, "muzzle-flash-light"); _animPlayer.Play((gunUid, uidPlayer), animTwo, "muzzle-flash-light");
} }
// TODO: Move RangedDamageSoundComponent to shared so this can be predicted.
public override void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound) {}
} }

View File

@@ -243,7 +243,7 @@ public sealed partial class SpaceVillainGame
UpdateUi( UpdateUi(
uid, uid,
Loc.GetString("space-villain-game-player-loses-message"), Loc.GetString("space-villain-game-player-loses-message"),
Loc.GetString("space-villain-game-enemy-dies-with-player-message ", ("enemyName", _villainName)), Loc.GetString("space-villain-game-enemy-dies-with-player-message", ("enemyName", _villainName)),
true true
); );
_audioSystem.PlayPvs(arcade.GameOverSound, uid, AudioParams.Default.WithVolume(-4f)); _audioSystem.PlayPvs(arcade.GameOverSound, uid, AudioParams.Default.WithVolume(-4f));

View File

@@ -56,7 +56,7 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
return; return;
args.Handled = true; args.Handled = true;
if (TryComp(ent, out LimitedChargesComponent? charges) && charges.LastCharges < ent.Comp.DecalChargeCost) if (TryComp(ent, out LimitedChargesComponent? charges) && _charges.GetCurrentCharges((ent, charges)) < ent.Comp.DecalChargeCost)
{ {
_popup.PopupEntity(Loc.GetString("spray-painter-interact-no-charges"), args.User, args.User); _popup.PopupEntity(Loc.GetString("spray-painter-interact-no-charges"), args.User, args.User);
return; return;
@@ -165,7 +165,7 @@ public sealed class SprayPainterSystem : SharedSprayPainterSystem
return; return;
if (TryComp<LimitedChargesComponent>(args.Used, out var charges) if (TryComp<LimitedChargesComponent>(args.Used, out var charges)
&& charges.LastCharges < painter.PipeChargeCost) && _charges.GetCurrentCharges((args.Used, charges)) < painter.PipeChargeCost)
{ {
var msg = Loc.GetString("spray-painter-interact-no-charges"); var msg = Loc.GetString("spray-painter-interact-no-charges");
_popup.PopupEntity(msg, args.User, args.User); _popup.PopupEntity(msg, args.User, args.User);

View File

@@ -1,27 +1,22 @@
using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Weapons.Ranged.Components; using Content.Server.Weapons.Ranged.Components;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Effects;
using Content.Shared.Projectiles; using Content.Shared.Projectiles;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.Weapons.Reflect; using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Damage.Components; using Content.Shared.Weapons.Hitscan.Events;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Containers;
namespace Content.Server.Weapons.Ranged.Systems; namespace Content.Server.Weapons.Ranged.Systems;
@@ -29,9 +24,6 @@ public sealed partial class GunSystem : SharedGunSystem
{ {
[Dependency] private readonly DamageExamineSystem _damageExamine = default!; [Dependency] private readonly DamageExamineSystem _damageExamine = default!;
[Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly SharedMapSystem _map = default!;
private const float DamagePitchVariation = 0.05f; private const float DamagePitchVariation = 0.05f;
@@ -103,6 +95,7 @@ public sealed partial class GunSystem : SharedGunSystem
continue; continue;
} }
// TODO: Clean this up in a gun refactor at some point - too much copy pasting
switch (shootable) switch (shootable)
{ {
// Cartridge shoots something else // Cartridge shoots something else
@@ -141,107 +134,21 @@ public sealed partial class GunSystem : SharedGunSystem
CreateAndFireProjectiles(ent.Value, newAmmo); CreateAndFireProjectiles(ent.Value, newAmmo);
break; break;
case HitscanPrototype hitscan: case HitscanAmmoComponent:
if (ent == null)
break;
EntityUid? lastHit = null; var hitscanEv = new HitscanTraceEvent
var from = fromMap;
// can't use map coords above because funny FireEffects
var fromEffect = fromCoordinates;
var dir = mapDirection.Normalized();
//in the situation when user == null, means that the cannon fires on its own (via signals). And we need the gun to not fire by itself in this case
var lastUser = user ?? gunUid;
if (hitscan.Reflective != ReflectType.None)
{ {
for (var reflectAttempt = 0; reflectAttempt < 3; reflectAttempt++) FromCoordinates = fromCoordinates,
{ ShotDirection = mapDirection.Normalized(),
var ray = new CollisionRay(from.Position, dir, hitscan.CollisionMask); Gun = gunUid,
var rayCastResults = Shooter = user,
Physics.IntersectRay(from.MapId, ray, hitscan.MaxLength, lastUser, false).ToList(); Target = gun.Target,
if (!rayCastResults.Any()) };
break; RaiseLocalEvent(ent.Value, ref hitscanEv);
var result = rayCastResults[0]; Del(ent);
// Check if laser is shot from in a container
if (!_container.IsEntityOrParentInContainer(lastUser))
{
// Checks if the laser should pass over unless targeted by its user
foreach (var collide in rayCastResults)
{
if (collide.HitEntity != gun.Target &&
CompOrNull<RequireProjectileTargetComponent>(collide.HitEntity)?.Active == true)
{
continue;
}
result = collide;
break;
}
}
var hit = result.HitEntity;
lastHit = hit;
FireEffects(fromEffect, result.Distance, dir.Normalized().ToAngle(), hitscan, hit);
var ev = new HitScanReflectAttemptEvent(user, gunUid, hitscan.Reflective, dir, false);
RaiseLocalEvent(hit, ref ev);
if (!ev.Reflected)
break;
fromEffect = Transform(hit).Coordinates;
from = TransformSystem.ToMapCoordinates(fromEffect);
dir = ev.Direction;
lastUser = hit;
}
}
if (lastHit != null)
{
var hitEntity = lastHit.Value;
if (hitscan.StaminaDamage > 0f)
_stamina.TakeStaminaDamage(hitEntity, hitscan.StaminaDamage, source: user);
var dmg = hitscan.Damage;
var hitName = ToPrettyString(hitEntity);
if (dmg != null)
dmg = Damageable.TryChangeDamage(hitEntity, dmg * Damageable.UniversalHitscanDamageModifier, origin: user);
// check null again, as TryChangeDamage returns modified damage values
if (dmg != null)
{
if (!Deleted(hitEntity))
{
if (dmg.AnyPositive())
{
_color.RaiseEffect(Color.Red, new List<EntityUid>() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager));
}
// TODO get fallback position for playing hit sound.
PlayImpactSound(hitEntity, dmg, hitscan.Sound, hitscan.ForceSound);
}
if (user != null)
{
Logs.Add(LogType.HitScanHit,
$"{ToPrettyString(user.Value):user} hit {hitName:target} using hitscan and dealt {dmg.GetTotal():damage} damage");
}
else
{
Logs.Add(LogType.HitScanHit,
$"{hitName:target} hit by hitscan dealing {dmg.GetTotal():damage} damage");
}
}
}
else
{
FireEffects(fromEffect, hitscan.MaxLength, dir.ToAngle(), hitscan);
}
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
break; break;
@@ -353,7 +260,7 @@ public sealed partial class GunSystem : SharedGunSystem
RaiseNetworkEvent(message, filter); RaiseNetworkEvent(message, filter);
} }
public void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound) public override void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound)
{ {
DebugTools.Assert(!Deleted(otherEntity), "Impact sound entity was deleted"); DebugTools.Assert(!Deleted(otherEntity), "Impact sound entity was deleted");
@@ -384,69 +291,4 @@ public sealed partial class GunSystem : SharedGunSystem
Audio.PlayPvs(weaponSound, otherEntity); Audio.PlayPvs(weaponSound, otherEntity);
} }
} }
// TODO: Pseudo RNG so the client can predict these.
#region Hitscan effects
private void FireEffects(EntityCoordinates fromCoordinates, float distance, Angle angle, HitscanPrototype hitscan, EntityUid? hitEntity = null)
{
// Lord
// Forgive me for the shitcode I am about to do
// Effects tempt me not
var sprites = new List<(NetCoordinates coordinates, Angle angle, SpriteSpecifier sprite, float scale)>();
var fromXform = Transform(fromCoordinates.EntityId);
// We'll get the effects relative to the grid / map of the firer
// Look you could probably optimise this a bit with redundant transforms at this point.
var gridUid = fromXform.GridUid;
if (gridUid != fromCoordinates.EntityId && TryComp(gridUid, out TransformComponent? gridXform))
{
var (_, gridRot, gridInvMatrix) = TransformSystem.GetWorldPositionRotationInvMatrix(gridXform);
var map = TransformSystem.ToMapCoordinates(fromCoordinates);
fromCoordinates = new EntityCoordinates(gridUid.Value, Vector2.Transform(map.Position, gridInvMatrix));
angle -= gridRot;
}
else
{
angle -= TransformSystem.GetWorldRotation(fromXform);
}
if (distance >= 1f)
{
if (hitscan.MuzzleFlash != null)
{
var coords = fromCoordinates.Offset(angle.ToVec().Normalized() / 2);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, angle, hitscan.MuzzleFlash, 1f));
}
if (hitscan.TravelFlash != null)
{
var coords = fromCoordinates.Offset(angle.ToVec() * (distance + 0.5f) / 2);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, angle, hitscan.TravelFlash, distance - 1.5f));
}
}
if (hitscan.ImpactFlash != null)
{
var coords = fromCoordinates.Offset(angle.ToVec() * distance);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, angle.FlipPositive(), hitscan.ImpactFlash, 1f));
}
if (sprites.Count > 0)
{
RaiseNetworkEvent(new HitscanEvent
{
Sprites = sprites,
}, Filter.Pvs(fromCoordinates, entityMan: EntityManager));
}
}
#endregion
} }

View File

@@ -37,7 +37,6 @@ namespace Content.Shared.Chemistry.Components
/// systems use this. /// systems use this.
/// </remarks> /// </remarks>
[DataField("maxVol")] [DataField("maxVol")]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero; public FixedPoint2 MaxVolume { get; set; } = FixedPoint2.Zero;
public float FillFraction => MaxVolume == 0 ? 1 : Volume.Float() / MaxVolume.Float(); public float FillFraction => MaxVolume == 0 ? 1 : Volume.Float() / MaxVolume.Float();
@@ -45,8 +44,7 @@ namespace Content.Shared.Chemistry.Components
/// <summary> /// <summary>
/// If reactions will be checked for when adding reagents to the container. /// If reactions will be checked for when adding reagents to the container.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("canReact")]
public bool CanReact { get; set; } = true; public bool CanReact { get; set; } = true;
/// <summary> /// <summary>
@@ -58,8 +56,7 @@ namespace Content.Shared.Chemistry.Components
/// <summary> /// <summary>
/// The temperature of the reagents in the solution. /// The temperature of the reagents in the solution.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("temperature")]
public float Temperature { get; set; } = 293.15f; public float Temperature { get; set; } = 293.15f;
/// <summary> /// <summary>
@@ -100,7 +97,7 @@ namespace Content.Shared.Chemistry.Components
_heatCapacity = 0; _heatCapacity = 0;
foreach (var (reagent, quantity) in Contents) foreach (var (reagent, quantity) in Contents)
{ {
_heatCapacity += (float) quantity * _heatCapacity += (float)quantity *
protoMan.Index<ReagentPrototype>(reagent.Prototype).SpecificHeat; protoMan.Index<ReagentPrototype>(reagent.Prototype).SpecificHeat;
} }
@@ -148,7 +145,7 @@ namespace Content.Shared.Chemistry.Components
/// </summary> /// </summary>
/// <param name="prototype">The prototype ID of the reagent to add.</param> /// <param name="prototype">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param> /// <param name="quantity">The quantity in milli-units.</param>
public Solution(string prototype, FixedPoint2 quantity, List<ReagentData>? data = null) : this() public Solution([ForbidLiteral] string prototype, FixedPoint2 quantity, List<ReagentData>? data = null) : this()
{ {
AddReagent(new ReagentId(prototype, data), quantity); AddReagent(new ReagentId(prototype, data), quantity);
} }
@@ -190,7 +187,7 @@ namespace Content.Shared.Chemistry.Components
public void ValidateSolution() public void ValidateSolution()
{ {
// sandbox forbids: [Conditional("DEBUG")] // sandbox forbids: [Conditional("DEBUG")]
#if DEBUG #if DEBUG
// Correct volume // Correct volume
DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume); DebugTools.Assert(Contents.Select(x => x.Quantity).Sum() == Volume);
@@ -208,7 +205,7 @@ namespace Content.Shared.Chemistry.Components
UpdateHeatCapacity(null); UpdateHeatCapacity(null);
DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur, tolerance: 0.01)); DebugTools.Assert(MathHelper.CloseTo(_heatCapacity, cur, tolerance: 0.01));
} }
#endif #endif
} }
void ISerializationHooks.AfterDeserialization() void ISerializationHooks.AfterDeserialization()
@@ -223,7 +220,7 @@ namespace Content.Shared.Chemistry.Components
MaxVolume = Volume; MaxVolume = Volume;
} }
public bool ContainsPrototype(string prototype) public bool ContainsPrototype([ForbidLiteral] string prototype)
{ {
foreach (var (reagent, _) in Contents) foreach (var (reagent, _) in Contents)
{ {
@@ -245,7 +242,7 @@ namespace Content.Shared.Chemistry.Components
return false; return false;
} }
public bool ContainsReagent(string reagentId, List<ReagentData>? data) public bool ContainsReagent([ForbidLiteral] string reagentId, List<ReagentData>? data)
=> ContainsReagent(new(reagentId, data)); => ContainsReagent(new(reagentId, data));
public bool TryGetReagent(ReagentId id, out ReagentQuantity quantity) public bool TryGetReagent(ReagentId id, out ReagentQuantity quantity)
@@ -352,7 +349,7 @@ namespace Content.Shared.Chemistry.Components
/// </summary> /// </summary>
/// <param name="prototype">The prototype ID of the reagent to add.</param> /// <param name="prototype">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param> /// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(string prototype, FixedPoint2 quantity, bool dirtyHeatCap = true) public void AddReagent([ForbidLiteral] string prototype, FixedPoint2 quantity, bool dirtyHeatCap = true)
=> AddReagent(new ReagentId(prototype, null), quantity, dirtyHeatCap); => AddReagent(new ReagentId(prototype, null), quantity, dirtyHeatCap);
/// <summary> /// <summary>
@@ -673,6 +670,12 @@ namespace Content.Shared.Chemistry.Components
return sol; return sol;
} }
/// <summary>
/// Splits a solution into two by moving reagents from the given solution into a new one.
/// This modifies the original solution.
/// </summary>
/// <param name="toTake">The quantity of this solution to remove.</param>
/// <returns>A new solution containing the removed reagents.</returns>
public Solution SplitSolution(FixedPoint2 toTake) public Solution SplitSolution(FixedPoint2 toTake)
{ {
if (toTake <= FixedPoint2.Zero) if (toTake <= FixedPoint2.Zero)
@@ -690,7 +693,7 @@ namespace Content.Shared.Chemistry.Components
var origVol = Volume; var origVol = Volume;
var effVol = Volume.Value; var effVol = Volume.Value;
newSolution = new Solution(Contents.Count) { Temperature = Temperature }; newSolution = new Solution(Contents.Count) { Temperature = Temperature };
var remaining = (long) toTake.Value; var remaining = (long)toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap. for (var i = Contents.Count - 1; i >= 0; i--) // iterate backwards because of remove swap.
{ {
@@ -706,7 +709,7 @@ namespace Content.Shared.Chemistry.Components
continue; continue;
} }
var splitQuantity = FixedPoint2.FromCents((int) split); var splitQuantity = FixedPoint2.FromCents((int)split);
var newQuantity = quantity - splitQuantity; var newQuantity = quantity - splitQuantity;
DebugTools.Assert(newQuantity >= 0); DebugTools.Assert(newQuantity >= 0);
@@ -753,7 +756,7 @@ namespace Content.Shared.Chemistry.Components
var effVol = Volume.Value; var effVol = Volume.Value;
Volume -= toTake; Volume -= toTake;
var remaining = (long) toTake.Value; var remaining = (long)toTake.Value;
for (var i = Contents.Count - 1; i >= 0; i--)// iterate backwards because of remove swap. for (var i = Contents.Count - 1; i >= 0; i--)// iterate backwards because of remove swap.
{ {
var (reagent, quantity) = Contents[i]; var (reagent, quantity) = Contents[i];
@@ -768,7 +771,7 @@ namespace Content.Shared.Chemistry.Components
continue; continue;
} }
var splitQuantity = FixedPoint2.FromCents((int) split); var splitQuantity = FixedPoint2.FromCents((int)split);
var newQuantity = quantity - splitQuantity; var newQuantity = quantity - splitQuantity;
if (newQuantity > FixedPoint2.Zero) if (newQuantity > FixedPoint2.Zero)

View File

@@ -588,7 +588,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// Adds a solution to the container, if it can fully fit. /// Adds a solution to the container, if it can fully fit.
/// </summary> /// </summary>
/// <param name="targetUid">entity holding targetSolution</param> /// <param name="targetUid">entity holding targetSolution</param>
/// <param name="targetSolution">entity holding targetSolution</param> /// <param name="targetSolution">entity holding targetSolution</param>
/// <param name="toAdd">solution being added</param> /// <param name="toAdd">solution being added</param>
/// <returns>If the solution could be added.</returns> /// <returns>If the solution could be added.</returns>
public bool TryAddSolution(Entity<SolutionComponent> soln, Solution toAdd) public bool TryAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
@@ -606,40 +606,44 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
} }
/// <summary> /// <summary>
/// Adds as much of a solution to a container as can fit. /// Adds as much of a solution to a container as can fit and updates the container.
/// </summary> /// </summary>
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param> /// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
/// <param name="targetSolution">The solution being added to.</param> /// <param name="targetSolution">The solution being added to.</param>
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param> /// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/>. This solution is not modified.</param>
/// <returns>The quantity of the solution actually added.</returns> /// <returns>The quantity of the solution actually added.</returns>
public FixedPoint2 AddSolution(Entity<SolutionComponent> soln, Solution toAdd) public FixedPoint2 AddSolution(Entity<SolutionComponent> soln, Solution toAdd)
{ {
var (uid, comp) = soln; var solution = soln.Comp.Solution;
var solution = comp.Solution;
if (toAdd.Volume == FixedPoint2.Zero) if (toAdd.Volume == FixedPoint2.Zero)
return FixedPoint2.Zero; return FixedPoint2.Zero;
var quantity = FixedPoint2.Max(FixedPoint2.Zero, FixedPoint2.Min(toAdd.Volume, solution.AvailableVolume)); var quantity = FixedPoint2.Max(FixedPoint2.Zero, FixedPoint2.Min(toAdd.Volume, solution.AvailableVolume));
if (quantity < toAdd.Volume) if (quantity < toAdd.Volume)
TryTransferSolution(soln, toAdd, quantity); {
// TODO: This should be made into a function that directly transfers reagents.
// Currently this is quite inefficient.
solution.AddSolution(toAdd.Clone().SplitSolution(quantity), PrototypeManager);
}
else else
ForceAddSolution(soln, toAdd); solution.AddSolution(toAdd, PrototypeManager);
UpdateChemicals(soln);
return quantity; return quantity;
} }
/// <summary> /// <summary>
/// Adds a solution to a container and updates the container. /// Adds a solution to a container and updates the container.
/// This can exceed the maximum volume of the solution added to.
/// </summary> /// </summary>
/// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param> /// <param name="targetUid">The entity containing <paramref cref="targetSolution"/></param>
/// <param name="targetSolution">The solution being added to.</param> /// <param name="targetSolution">The solution being added to.</param>
/// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/></param> /// <param name="toAdd">The solution being added to <paramref cref="targetSolution"/>. This solution is not modified.</param>
/// <returns>Whether any reagents were added to the solution.</returns> /// <returns>Whether any reagents were added to the solution.</returns>
public bool ForceAddSolution(Entity<SolutionComponent> soln, Solution toAdd) public bool ForceAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
{ {
var (uid, comp) = soln; var solution = soln.Comp.Solution;
var solution = comp.Solution;
if (toAdd.Volume == FixedPoint2.Zero) if (toAdd.Volume == FixedPoint2.Zero)
return false; return false;

View File

@@ -0,0 +1,11 @@
using Content.Shared.Weapons.Ranged;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// This component is used to indicate an entity is shootable from a hitscan weapon.
/// This is placed on the laser entity being shot, not the gun itself.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanAmmoComponent : Component, IShootable;

View File

@@ -0,0 +1,17 @@
using Content.Shared.Damage;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// Hitscan entities that have this component will do the damage specified to hit targets (Who didn't reflect it).
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanBasicDamageComponent : Component
{
/// <summary>
/// How much damage the hitscan weapon will do when hitting a target.
/// </summary>
[DataField(required: true)]
public DamageSpecifier Damage;
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// System or basic "effects" like sounds and hit markers for hitscans.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanBasicEffectsComponent : Component
{
/// <summary>
/// This will turn hit entities this color briefly.
/// </summary>
[DataField]
public Color? HitColor = Color.Red;
/// <summary>
/// Sound that plays upon the thing being hit.
/// </summary>
[DataField]
public SoundSpecifier? Sound;
/// <summary>
/// Force the hitscan sound to play rather than playing the entity's override sound (if it exists).
/// </summary>
[DataField]
public bool ForceSound;
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Physics;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// A basic raycast system that will shoot in a straight line when triggered.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanBasicRaycastComponent : Component
{
/// <summary>
/// Maximum distance the raycast will travel before giving up. Reflections will reset the distance traveled
/// </summary>
[DataField]
public float MaxDistance = 20.0f;
/// <summary>
/// The collision mask the hitscan ray uses to collide with other objects. See the enum for more information
/// </summary>
[DataField]
public CollisionGroup CollisionMask = CollisionGroup.Opaque;
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// Provides basic visuals for hitscan weapons - works with <see cref="HitscanBasicRaycastComponent"/>
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanBasicVisualsComponent : Component
{
/// <summary>
/// The muzzle flash from the hitscan weapon.
/// </summary>
[DataField]
public SpriteSpecifier? MuzzleFlash;
/// <summary>
/// The "travel" sprite, this gets repeated until it hits the target.
/// </summary>
[DataField]
public SpriteSpecifier? TravelFlash;
/// <summary>
/// The sprite that gets shown on the impact of the laser.
/// </summary>
[DataField]
public SpriteSpecifier? ImpactFlash;
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.Weapons.Reflect;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// Hitscan entities with this component will get reflected by certain things (E.G energy swords).
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanReflectComponent : Component
{
/// <summary>
/// The reflective type, will only reflect from entities that have a matching reflection type.
/// </summary>
[DataField]
public ReflectType ReflectiveType = ReflectType.Energy;
/// <summary>
/// The maximum number of reflections the laser will make. <see cref="CurrentReflections"/>
/// </summary>
[DataField]
public int MaxReflections = 3;
/// <summary>
/// Current number of times this hitscan entity was reflected. Will not be more than <see cref="MaxReflections"/>
/// </summary>
[DataField]
public int CurrentReflections;
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Hitscan.Components;
/// <summary>
/// Hitscan entities that have this component will deal stamina damage to the target.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class HitscanStaminaDamageComponent : Component
{
/// <summary>
/// How much stamania damage the hitscan weapon will do when hitting a target.
/// </summary>
[DataField]
public float StaminaDamage = 10.0f;
}

View File

@@ -0,0 +1,110 @@
using System.Numerics;
using Content.Shared.Damage;
using Robust.Shared.Map;
namespace Content.Shared.Weapons.Hitscan.Events;
/// <summary>
/// Raised on the hitscan entity when "fired". This could be from reflections or from the gun. This is the catalyst that
/// other systems will listen for to actually shoot the gun.
/// </summary>
[ByRefEvent]
public record struct HitscanTraceEvent
{
/// <summary>
/// Location the hitscan was fired from.
/// </summary>
public EntityCoordinates FromCoordinates;
/// <summary>
/// Direction that the ray was fired towards.
/// </summary>
public Vector2 ShotDirection;
/// <summary>
/// Gun that was fired - this will always be the original weapon even if reflected.
/// </summary>
public EntityUid Gun;
/// <summary>
/// Player who shot the gun, if null the gun was fired by itself.
/// </summary>
public EntityUid? Shooter;
/// <summary>
/// Target that was being aimed at (Not necessarily hit).
/// </summary>
public EntityUid? Target;
}
/// <summary>
/// All data known data for when a hitscan is actually fired.
/// </summary>
public record struct HitscanRaycastFiredData
{
/// <summary>
/// Direction that the ray was fired towards.
/// </summary>
public Vector2 ShotDirection;
/// <summary>
/// The entity that got hit, if null the raycast didn't hit anyone.
/// </summary>
public EntityUid? HitEntity;
/// <summary>
/// Gun that fired the raycast.
/// </summary>
public EntityUid Gun;
/// <summary>
/// Player who shot the gun, if null the gun was fired by itself.
/// </summary>
public EntityUid? Shooter;
}
/// <summary>
/// Try to hit the targeted entity with a hitscan laser. Stuff like the reflection system should listen for this and
/// cancel the event if the laser was reflected.
/// </summary>
[ByRefEvent]
public struct AttemptHitscanRaycastFiredEvent
{
/// <summary>
/// Data for the hitscan that was fired.
/// </summary>
public HitscanRaycastFiredData Data;
/// <summary>
/// Set to true the hitscan is cancelled (e.g. due to reflection).
/// Cancelled hitscans should not apply damage or trigger follow-up effects.
/// </summary>
public bool Cancelled;
}
/// <summary>
/// Results of a hitscan raycast and will be raised on the raycast entity on itself. Stuff like the damage system should
/// listen for this. At this point we KNOW the laser hit the entity.
/// </summary>
[ByRefEvent]
public struct HitscanRaycastFiredEvent
{
/// <summary>
/// Data for the hitscan that was fired.
/// </summary>
public HitscanRaycastFiredData Data;
}
[ByRefEvent]
public record struct HitscanDamageDealtEvent
{
/// <summary>
/// Target that was dealt damage.
/// </summary>
public EntityUid Target;
/// <summary>
/// The amount of damage that the target was dealt.
/// </summary>
public DamageSpecifier DamageDealt;
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Damage;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Hitscan.Events;
namespace Content.Shared.Weapons.Hitscan.Systems;
public sealed class HitscanBasicDamageSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damage = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HitscanBasicDamageComponent, HitscanRaycastFiredEvent>(OnHitscanHit);
}
private void OnHitscanHit(Entity<HitscanBasicDamageComponent> ent, ref HitscanRaycastFiredEvent args)
{
if (args.Data.HitEntity == null)
return;
var dmg = ent.Comp.Damage * _damage.UniversalHitscanDamageModifier;
var damageDealt = _damage.TryChangeDamage(args.Data.HitEntity, dmg, origin: args.Data.Gun);
if (damageDealt == null)
return;
var damageEvent = new HitscanDamageDealtEvent
{
Target = args.Data.HitEntity.Value,
DamageDealt = damageDealt,
};
RaiseLocalEvent(ent, ref damageEvent);
}
}

View File

@@ -0,0 +1,36 @@
using Content.Shared.Damage;
using Content.Shared.Effects;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Hitscan.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Player;
namespace Content.Shared.Weapons.Hitscan.Systems;
public sealed class HitscanBasicEffectsSystem : EntitySystem
{
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly SharedGunSystem _gun = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HitscanBasicEffectsComponent, HitscanDamageDealtEvent>(OnHitscanDamageDealt);
}
private void OnHitscanDamageDealt(Entity<HitscanBasicEffectsComponent> ent, ref HitscanDamageDealtEvent args)
{
if (Deleted(args.Target))
return;
if (ent.Comp.HitColor != null && args.DamageDealt.GetTotal() != 0)
{
_color.RaiseEffect(ent.Comp.HitColor.Value,
new List<EntityUid> { args.Target },
Filter.Pvs(args.Target, entityManager: EntityManager));
}
_gun.PlayImpactSound(args.Target, args.DamageDealt, ent.Comp.Sound, ent.Comp.ForceSound);
}
}

View File

@@ -0,0 +1,150 @@
using System.Numerics;
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Components;
using Content.Shared.Database;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Hitscan.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Shared.Weapons.Hitscan.Systems;
public sealed class HitscanBasicRaycastSystem : EntitySystem
{
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly ISharedAdminLogManager _log = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<HitscanBasicVisualsComponent> _visualsQuery;
public override void Initialize()
{
base.Initialize();
_visualsQuery = GetEntityQuery<HitscanBasicVisualsComponent>();
SubscribeLocalEvent<HitscanBasicRaycastComponent, HitscanTraceEvent>(OnHitscanFired);
}
private void OnHitscanFired(Entity<HitscanBasicRaycastComponent> ent, ref HitscanTraceEvent args)
{
var shooter = args.Shooter ?? args.Gun;
var mapCords = _transform.ToMapCoordinates(args.FromCoordinates);
var ray = new CollisionRay(mapCords.Position, args.ShotDirection, (int) ent.Comp.CollisionMask);
var rayCastResults = _physics.IntersectRay(mapCords.MapId, ray, ent.Comp.MaxDistance, shooter, false);
var target = args.Target;
// If you are in a container, use the raycast result
// Otherwise:
// 1.) Hit the first entity that you targeted.
// 2.) Hit the first entity that doesn't require you to aim at it specifically to be hit.
var result = _container.IsEntityOrParentInContainer(shooter)
? rayCastResults.FirstOrNull()
: rayCastResults.FirstOrNull(hit => hit.HitEntity == target
|| CompOrNull<RequireProjectileTargetComponent>(hit.HitEntity)?.Active != true);
var distanceTried = result?.Distance ?? ent.Comp.MaxDistance;
// Do visuals without an event. They should always happen and putting it on the attempt event is weird!
// If more stuff gets added here, it should probably be turned into an event.
FireEffects(args.FromCoordinates, distanceTried, args.ShotDirection.ToAngle(), ent.Owner);
// Admin logging
if (result?.HitEntity != null)
{
_log.Add(LogType.HitScanHit,
$"{ToPrettyString(shooter):user} hit {ToPrettyString(result.Value.HitEntity):target}"
+ $" using {ToPrettyString(args.Gun):entity}.");
}
var data = new HitscanRaycastFiredData
{
ShotDirection = args.ShotDirection,
Gun = args.Gun,
Shooter = args.Shooter,
HitEntity = result?.HitEntity,
};
var attemptEvent = new AttemptHitscanRaycastFiredEvent { Data = data };
RaiseLocalEvent(ent, ref attemptEvent);
if (attemptEvent.Cancelled)
return;
var hitEvent = new HitscanRaycastFiredEvent { Data = data };
RaiseLocalEvent(ent, ref hitEvent);
}
/// <summary>
/// Create visual effects for the fired hitscan weapon.
/// </summary>
/// <param name="fromCoordinates">Location to start the effect.</param>
/// <param name="distance">Distance of the hitscan shot.</param>
/// <param name="shotAngle">Angle of the shot.</param>
/// <param name="hitscanUid">The hitscan entity itself.</param>
private void FireEffects(EntityCoordinates fromCoordinates, float distance, Angle shotAngle, EntityUid hitscanUid)
{
if (distance == 0 || !_visualsQuery.TryComp(hitscanUid, out var vizComp))
return;
var sprites = new List<(NetCoordinates coordinates, Angle angle, SpriteSpecifier sprite, float scale)>();
var fromXform = Transform(fromCoordinates.EntityId);
// We'll get the effects relative to the grid / map of the firer
// Look you could probably optimise this a bit with redundant transforms at this point.
var gridUid = fromXform.GridUid;
if (gridUid != fromCoordinates.EntityId && TryComp(gridUid, out TransformComponent? gridXform))
{
var (_, gridRot, gridInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(gridXform);
var map = _transform.ToMapCoordinates(fromCoordinates);
fromCoordinates = new EntityCoordinates(gridUid.Value, Vector2.Transform(map.Position, gridInvMatrix));
shotAngle -= gridRot;
}
else
{
shotAngle -= _transform.GetWorldRotation(fromXform);
}
if (distance >= 1f)
{
if (vizComp.MuzzleFlash != null)
{
var coords = fromCoordinates.Offset(shotAngle.ToVec().Normalized() / 2);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, shotAngle, vizComp.MuzzleFlash, 1f));
}
if (vizComp.TravelFlash != null)
{
var coords = fromCoordinates.Offset(shotAngle.ToVec() * (distance + 0.5f) / 2);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, shotAngle, vizComp.TravelFlash, distance - 1.5f));
}
}
if (vizComp.ImpactFlash != null)
{
var coords = fromCoordinates.Offset(shotAngle.ToVec() * distance);
var netCoords = GetNetCoordinates(coords);
sprites.Add((netCoords, shotAngle.FlipPositive(), vizComp.ImpactFlash, 1f));
}
if (sprites.Count > 0)
{
RaiseNetworkEvent(new SharedGunSystem.HitscanEvent
{
Sprites = sprites,
}, Filter.Pvs(fromCoordinates, entityMan: EntityManager));
}
}
}

View File

@@ -0,0 +1,50 @@
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Hitscan.Events;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Reflect;
using Robust.Shared.Random;
namespace Content.Shared.Weapons.Hitscan.Systems;
public sealed class HitscanReflectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HitscanReflectComponent, AttemptHitscanRaycastFiredEvent>(OnHitscanHit);
}
private void OnHitscanHit(Entity<HitscanReflectComponent> hitscan, ref AttemptHitscanRaycastFiredEvent args)
{
var data = args.Data;
if (hitscan.Comp.ReflectiveType == ReflectType.None || data.HitEntity == null)
return;
if (hitscan.Comp.CurrentReflections >= hitscan.Comp.MaxReflections)
return;
var ev = new HitScanReflectAttemptEvent(data.Shooter ?? data.Gun, data.Gun, hitscan.Comp.ReflectiveType, data.ShotDirection, false);
RaiseLocalEvent(data.HitEntity.Value, ref ev);
if (!ev.Reflected)
return;
hitscan.Comp.CurrentReflections++;
args.Cancelled = true;
var fromEffect = Transform(data.HitEntity.Value).Coordinates;
var hitFiredEvent = new HitscanTraceEvent
{
FromCoordinates = fromEffect,
ShotDirection = ev.Direction,
Gun = data.Gun,
Shooter = data.HitEntity.Value,
};
RaiseLocalEvent(hitscan, ref hitFiredEvent);
}
}

View File

@@ -0,0 +1,25 @@
using Content.Shared.Damage.Systems;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Hitscan.Events;
namespace Content.Shared.Weapons.Hitscan.Systems;
public sealed class HitscanStunSystem : EntitySystem
{
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HitscanStaminaDamageComponent, HitscanRaycastFiredEvent>(OnHitscanHit);
}
private void OnHitscanHit(Entity<HitscanStaminaDamageComponent> hitscan, ref HitscanRaycastFiredEvent args)
{
if (args.Data.HitEntity == null)
return;
_stamina.TakeStaminaDamage(args.Data.HitEntity.Value, hitscan.Comp.StaminaDamage, source: args.Data.Shooter ?? args.Data.Gun);
}
}

View File

@@ -1,11 +1,11 @@
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Prototypes;
namespace Content.Shared.Weapons.Ranged.Components; namespace Content.Shared.Weapons.Ranged.Components;
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class HitscanBatteryAmmoProviderComponent : BatteryAmmoProviderComponent public sealed partial class HitscanBatteryAmmoProviderComponent : BatteryAmmoProviderComponent
{ {
[ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<HitscanPrototype>))] [DataField("proto", required: true)]
public string Prototype = default!; public EntProtoId HitscanEntityProto;
} }

View File

@@ -1,57 +0,0 @@
using Content.Shared.Damage;
using Content.Shared.Physics;
using Content.Shared.Weapons.Reflect;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Weapons.Ranged;
[Prototype]
public sealed partial class HitscanPrototype : IPrototype, IShootable
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[ViewVariables(VVAccess.ReadWrite), DataField("staminaDamage")]
public float StaminaDamage;
[ViewVariables(VVAccess.ReadWrite), DataField("damage")]
public DamageSpecifier? Damage;
[ViewVariables(VVAccess.ReadOnly), DataField("muzzleFlash")]
public SpriteSpecifier? MuzzleFlash;
[ViewVariables(VVAccess.ReadOnly), DataField("travelFlash")]
public SpriteSpecifier? TravelFlash;
[ViewVariables(VVAccess.ReadOnly), DataField("impactFlash")]
public SpriteSpecifier? ImpactFlash;
[DataField("collisionMask")]
public int CollisionMask = (int) CollisionGroup.Opaque;
/// <summary>
/// What we count as for reflection.
/// </summary>
[DataField("reflective")] public ReflectType Reflective = ReflectType.Energy;
/// <summary>
/// Sound that plays upon the thing being hit.
/// </summary>
[DataField("sound")]
public SoundSpecifier? Sound;
/// <summary>
/// Force the hitscan sound to play rather than potentially playing the entity's sound.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("forceSound")]
public bool ForceSound;
/// <summary>
/// Try not to set this too high.
/// </summary>
[DataField("maxLength")]
public float MaxLength = 20f;
}

View File

@@ -2,6 +2,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Events; using Content.Shared.Damage.Events;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Projectiles; using Content.Shared.Projectiles;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -80,11 +81,10 @@ public abstract partial class SharedGunSystem
{ {
if (component is ProjectileBatteryAmmoProviderComponent battery) if (component is ProjectileBatteryAmmoProviderComponent battery)
{ {
if (ProtoManager.Index<EntityPrototype>(battery.Prototype) if (ProtoManager.Index<EntityPrototype>(battery.Prototype).Components
.Components
.TryGetValue(Factory.GetComponentName<ProjectileComponent>(), out var projectile)) .TryGetValue(Factory.GetComponentName<ProjectileComponent>(), out var projectile))
{ {
var p = (ProjectileComponent)projectile.Component; var p = (ProjectileComponent) projectile.Component;
if (!p.Damage.Empty) if (!p.Damage.Empty)
{ {
@@ -97,8 +97,11 @@ public abstract partial class SharedGunSystem
if (component is HitscanBatteryAmmoProviderComponent hitscan) if (component is HitscanBatteryAmmoProviderComponent hitscan)
{ {
var dmg = ProtoManager.Index<HitscanPrototype>(hitscan.Prototype).Damage; var dmg = ProtoManager.Index(hitscan.HitscanEntityProto);
return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier; if (!dmg.TryGetComponent<HitscanBasicDamageComponent>(out var basicDamageComp, Factory))
return null;
return basicDamageComp.Damage * Damageable.UniversalHitscanDamageModifier;
} }
return null; return null;
@@ -155,7 +158,8 @@ public abstract partial class SharedGunSystem
var ent = Spawn(proj.Prototype, coordinates); var ent = Spawn(proj.Prototype, coordinates);
return (ent, EnsureShootable(ent)); return (ent, EnsureShootable(ent));
case HitscanBatteryAmmoProviderComponent hitscan: case HitscanBatteryAmmoProviderComponent hitscan:
return (null, ProtoManager.Index<HitscanPrototype>(hitscan.Prototype)); var hitscanEnt = Spawn(hitscan.HitscanEntityProto);
return (hitscanEnt, EnsureShootable(hitscanEnt));
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }

View File

@@ -16,6 +16,7 @@ using Content.Shared.Tag;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Timing; using Content.Shared.Timing;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
@@ -500,6 +501,9 @@ public abstract partial class SharedGunSystem : EntitySystem
if (TryComp<CartridgeAmmoComponent>(uid, out var cartridge)) if (TryComp<CartridgeAmmoComponent>(uid, out var cartridge))
return cartridge; return cartridge;
if (TryComp<HitscanAmmoComponent>(uid, out var hitscanAmmo))
return hitscanAmmo;
return EnsureComp<AmmoComponent>(uid); return EnsureComp<AmmoComponent>(uid);
} }
@@ -614,6 +618,8 @@ public abstract partial class SharedGunSystem : EntitySystem
protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null);
public abstract void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound);
/// <summary> /// <summary>
/// Used for animated effects on the client. /// Used for animated effects on the client.
/// </summary> /// </summary>

View File

@@ -56,6 +56,10 @@ public sealed partial class ReflectComponent : Component
public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg", AudioParams.Default.WithVariation(0.05f)); public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg", AudioParams.Default.WithVariation(0.05f));
} }
/// <summary>
/// Used for both the projectiles being reflected and the entities reflecting. If there is ever overlap between the
/// reflection types, the projectile will be reflected.
/// </summary>
[Flags, Serializable, NetSerializable] [Flags, Serializable, NetSerializable]
public enum ReflectType : byte public enum ReflectType : byte
{ {

View File

@@ -28,3 +28,14 @@
copyright: "raptorgoesextinct.wav by Nerdwizard78" copyright: "raptorgoesextinct.wav by Nerdwizard78"
source: "https://freesound.org/people/Nerdwizard78/sounds/643928/" source: "https://freesound.org/people/Nerdwizard78/sounds/643928/"
- files:
- vox_chitter.ogg
license: "CC0-1.0"
copyright: "dinosaur.wav by JhennaSide"
source: "https://freesound.org/people/JhennaSide/sounds/455906/"
- files:
- vox_click.ogg
license: "CC0-1.0"
copyright: "Storks bill-clapping by Breviceps"
source: "https://freesound.org/people/Breviceps/sounds/705861/"

Binary file not shown.

Binary file not shown.

View File

@@ -1,63 +1,4 @@
Entries: Entries:
- author: ScarKy0
changes:
- message: Increased the health of uranium windows, making them a reasonable upgrade
over regular plasma.
type: Tweak
id: 8611
time: '2025-06-03T18:20:09.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38040
- author: Moomoobeef
changes:
- message: Nanotask printouts now look much nicer!
type: Tweak
id: 8612
time: '2025-06-04T08:16:15.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/37805
- author: TheCactus, ilovehans10
changes:
- message: Added text highlighting.
type: Add
id: 8613
time: '2025-06-04T10:12:37.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/31442
- author: SlamBamActionman
changes:
- message: Diona can now root, keeping them from slipping but absorbing fluids from
the ground.
type: Add
id: 8614
time: '2025-06-04T10:53:00.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32782
- author: spanky-spanky
changes:
- message: Evac repair lockers, which contain equipment to repair shuttle bombing.
type: Add
id: 8615
time: '2025-06-05T06:08:38.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38075
- author: slarticodefast
changes:
- message: Fixed the game not launching in compability mode.
type: Fix
id: 8616
time: '2025-06-05T12:52:39.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38080
- author: Orsoniks
changes:
- message: Added more in-hand sprites for food items
type: Add
id: 8617
time: '2025-06-05T21:06:38.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38024
- author: chromiumboy
changes:
- message: Holopads now provide less visual coverage to station AIs (1 tile radius,
down from 7.5). Holopads must also be anchored to the floor to provide vision.
type: Tweak
id: 8618
time: '2025-06-05T23:15:55.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/38059
- author: Simyon - author: Simyon
changes: changes:
- message: Sleeper agents no longer have different codewords than round-start traitors. - message: Sleeper agents no longer have different codewords than round-start traitors.
@@ -3933,3 +3874,66 @@
id: 9112 id: 9112
time: '2025-10-16T18:47:31.0000000+00:00' time: '2025-10-16T18:47:31.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35636 url: https://github.com/space-wizards/space-station-14/pull/35636
- author: Wolfkey-SomeoneElseTookMyUsername
changes:
- message: The recipes for grilled cheese, cotton buns, cotton cakes, and cotton
grilled cheese are now in the correct category in the guidebook
type: Fix
id: 9113
time: '2025-10-17T18:56:01.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40949
- author: TrixxedHeart
changes:
- message: Added Enter/Leave Genpop access to Security by default, allowing them
to be able to fix Genpop turnstiles with the access configurator if they are
destroyed.
type: Tweak
id: 9114
time: '2025-10-18T00:28:44.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39515
- author: TrixxedHeart
changes:
- message: Added Vox Chitter and Clicking emotes
type: Add
id: 9115
time: '2025-10-18T00:28:44.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40878
- author: Kittygyat
changes:
- message: Added a new generic Artistry borg module!
type: Add
id: 9116
time: '2025-10-18T07:13:42.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/39679
- author: Hitlinemoss
changes:
- message: Folders and clipboards now recycle into sensible material components,
rather than only cardboard.
type: Fix
- message: Clipboards and plastic clipboards require slightly more steel to produce
in autolathes.
type: Tweak
id: 9117
time: '2025-10-18T09:25:00.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40954
- author: PicklOH
changes:
- message: Rags can no longer be used to remove evidence.
type: Remove
id: 9118
time: '2025-10-18T13:49:54.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40818
- author: MissKay1994
changes:
- message: Vox organs now have unique sprites.
type: Add
id: 9119
time: '2025-10-18T17:07:52.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40555
- author: Hitlinemoss
changes:
- message: Cargo orders containing beverages now ship in freezers.
type: Tweak
id: 9120
time: '2025-10-18T17:20:44.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/40955

View File

@@ -1,11 +1,11 @@
meta: meta:
format: 7 format: 7
category: Map category: Map
engineVersion: 267.2.0 engineVersion: 267.3.0
forkId: "" forkId: ""
forkVersion: "" forkVersion: ""
time: 10/08/2025 23:22:51 time: 10/16/2025 13:06:44
entityCount: 36080 entityCount: 36082
maps: maps:
- 1 - 1
grids: grids:
@@ -186578,6 +186578,29 @@ entities:
parent: 13329 parent: 13329
- type: Fixtures - type: Fixtures
fixtures: {} fixtures: {}
- proto: SignArrivals
entities:
- uid: 9339
components:
- type: Transform
pos: -37.5,-27.5
parent: 13329
- type: Fixtures
fixtures: {}
- uid: 9340
components:
- type: Transform
pos: -24.5,-7.5
parent: 13329
- type: Fixtures
fixtures: {}
- uid: 10642
components:
- type: Transform
pos: -24.5,-23.5
parent: 13329
- type: Fixtures
fixtures: {}
- proto: SignAtmos - proto: SignAtmos
entities: entities:
- uid: 26468 - uid: 26468
@@ -188492,14 +188515,6 @@ entities:
fixtures: {} fixtures: {}
- proto: SignShipDock - proto: SignShipDock
entities: entities:
- uid: 13601
components:
- type: Transform
rot: -1.5707963267948966 rad
pos: -37.5,-27.5
parent: 13329
- type: Fixtures
fixtures: {}
- uid: 19947 - uid: 19947
components: components:
- type: Transform - type: Transform

View File

@@ -28,6 +28,8 @@
tags: tags:
- Trash - Trash
isSpecialDigestibleExclusive: false isSpecialDigestibleExclusive: false
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: entity - type: entity
parent: OrganHumanLiver parent: OrganHumanLiver
@@ -37,6 +39,8 @@
components: components:
- type: Metabolizer - type: Metabolizer
metabolizerTypes: [Vox] metabolizerTypes: [Vox]
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: entity - type: entity
parent: OrganHumanHeart parent: OrganHumanHeart
@@ -46,3 +50,49 @@
components: components:
- type: Metabolizer - type: Metabolizer
metabolizerTypes: [Vox] metabolizerTypes: [Vox]
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: entity
parent: OrganHumanKidneys
id: OrganVoxKidneys
name: kidney
description: "Smells flammable."
components:
- type: Metabolizer
metabolizerTypes: [Vox]
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: entity
id: OrganVoxEyes
parent: OrganHumanEyes
name: eyes
components:
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: Item
size: Small
heldPrefix: eyeballs
- type: entity
id: OrganVoxTongueA
parent: OrganHumanTongue
name: tongue
description: "A fleshy muscle mostly used for screaming."
components:
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: Item
size: Small
- type: entity
id: OrganVoxTongueB
parent: OrganHumanTongue
name: tongue
description: "A fleshy muscle mostly used for screaming."
components:
- type: Sprite
sprite: Mobs/Species/Vox/organs.rsi
- type: Item
size: Small

View File

@@ -9,7 +9,7 @@
- torso - torso
organs: organs:
brain: OrganHumanBrain brain: OrganHumanBrain
eyes: OrganHumanEyes eyes: OrganVoxEyes
torso: torso:
part: TorsoVox part: TorsoVox
connections: connections:
@@ -22,7 +22,7 @@
lungs: OrganVoxLungs lungs: OrganVoxLungs
stomach: OrganVoxStomach stomach: OrganVoxStomach
liver: OrganVoxLiver liver: OrganVoxLiver
kidneys: OrganHumanKidneys kidneys: OrganVoxKidneys
right arm: right arm:
part: RightArmVox part: RightArmVox
connections: connections:

View File

@@ -25,7 +25,7 @@
- type: entity - type: entity
id: CrateFoodCooking id: CrateFoodCooking
parent: CratePlastic parent: CrateFreezer
name: kitchen supplies crate name: kitchen supplies crate
description: Extra kitchen supplies, in case the botanists are absent. description: Extra kitchen supplies, in case the botanists are absent.
components: components:
@@ -79,7 +79,7 @@
- type: entity - type: entity
id: CrateFoodBarSupply id: CrateFoodBarSupply
parent: CratePlastic parent: CrateFreezer
name: bartending supplies crate name: bartending supplies crate
description: Extra Bar supplies, in case the clown was allowed in the bar unsupervised. description: Extra Bar supplies, in case the clown was allowed in the bar unsupervised.
components: components:
@@ -106,7 +106,7 @@
- type: entity - type: entity
id: CrateFoodSoftdrinks id: CrateFoodSoftdrinks
parent: CratePlastic parent: CrateFreezer
name: softdrinks crate name: softdrinks crate
description: A variety of sodas to complement a small party, without having to empty the soda machines. Includes 14 sodas. description: A variety of sodas to complement a small party, without having to empty the soda machines. Includes 14 sodas.
components: components:
@@ -129,7 +129,7 @@
- type: entity - type: entity
id: CrateFoodGetMore id: CrateFoodGetMore
parent: CratePlastic parent: CrateFreezer
name: Getmore Bakemore crate name: Getmore Bakemore crate
description: Getmore branded snacks and baking supplies for the creative chef, all without the need of emptying your station's Getmore machines! description: Getmore branded snacks and baking supplies for the creative chef, all without the need of emptying your station's Getmore machines!
components: components:

View File

@@ -217,7 +217,7 @@
- type: entity - type: entity
id: CrateServiceSodaDispenser id: CrateServiceSodaDispenser
parent: CrateGenericSteel parent: CrateFreezer
name: soda dispenser refill crate name: soda dispenser refill crate
description: Contains refills for soda dispensers. description: Contains refills for soda dispensers.
components: components:
@@ -244,7 +244,7 @@
- type: entity - type: entity
id: CrateServiceBoozeDispenser id: CrateServiceBoozeDispenser
parent: CrateGenericSteel parent: CrateFreezer
name: booze dispenser refill crate name: booze dispenser refill crate
description: Contains refills for booze dispensers. description: Contains refills for booze dispensers.
components: components:

View File

@@ -15,6 +15,7 @@
- type: Speech - type: Speech
speechVerb: Vox speechVerb: Vox
speechSounds: Vox speechSounds: Vox
allowedEmotes: ['Click', 'Chitter']
- type: TypingIndicator - type: TypingIndicator
proto: vox proto: vox
- type: Vocal - type: Vocal

View File

@@ -1,7 +1,7 @@
- type: entity - type: entity
abstract: true abstract: true
parent: BaseItem parent: BaseItem
id: Crayon id: CrayonInedible
name: crayon name: crayon
description: A colourful crayon. Looks tasty. Mmmm... description: A colourful crayon. Looks tasty. Mmmm...
components: components:
@@ -24,7 +24,25 @@
selectedState: like selectedState: like
- type: LimitedCharges - type: LimitedCharges
maxCharges: 25 maxCharges: 25
- type: Food - type: StaticPrice
price: 5
- type: entity
abstract: true
parent: CrayonInedible
id: Crayon
name: crayon
description: A colourful crayon. Looks tasty. Mmmm...
components:
- type: Sprite
sprite: Objects/Fun/crayons.rsi
- type: Item
sprite: Objects/Fun/crayons.rsi
size: Tiny
- type: Tag
tags:
- Recyclable
- type: Edible
- type: FlavorProfile - type: FlavorProfile
flavors: flavors:
- chewy - chewy
@@ -37,8 +55,6 @@
Quantity: 3 Quantity: 3
- ReagentId: MindbreakerToxin - ReagentId: MindbreakerToxin
Quantity: 2 Quantity: 2
- type: StaticPrice
price: 5
- type: entity - type: entity
parent: Crayon parent: Crayon
@@ -109,6 +125,30 @@
- type: AutoRecharge - type: AutoRecharge
rechargeDuration: 5 rechargeDuration: 5
- type: entity
parent: CrayonInedible
id: CrayonBorg
name: electric crayon
description: Supposedly the most delicious crayon type in all the universes; unfortunately, you cannot eat.
components:
- type: Sprite
state: electric
- type: Item
heldPrefix: electric
- type: Crayon
deleteEmpty: false
color: Red
selectableColor: true
- type: LimitedCharges
maxCharges: 30
- type: AutoRecharge
rechargeDuration: 5
- type: Tag
tags:
- Write
- Crayon
- Trash
- type: entity - type: entity
parent: Crayon parent: Crayon
id: CrayonBlack id: CrayonBlack

View File

@@ -11,6 +11,9 @@
- state: folder-base - state: folder-base
- state: folder-stamp-inverse - state: folder-stamp-inverse
color: "#1dff00" color: "#1dff00"
- type: PhysicalComposition
materialComposition:
Paper: 50
- type: SpawnItemsOnUse - type: SpawnItemsOnUse
items: items:
- id: NukeCodePaper - id: NukeCodePaper
@@ -58,6 +61,11 @@
whitelist: whitelist:
tags: tags:
- Document - Document
- type: PhysicalComposition
materialComposition:
Paper: 50
- type: StaticPrice
price: 15 # Weirdly this is more expensive than cardboard boxes. But if I have this any lower then I get an arbitrage testfail because the raw materials are worth ~$14.
- type: Appearance - type: Appearance
- type: Tag - type: Tag
tags: tags:
@@ -304,6 +312,10 @@
slots: [belt] slots: [belt]
quickEquip: false quickEquip: false
sprite: Objects/Misc/clipboard.rsi sprite: Objects/Misc/clipboard.rsi
- type: PhysicalComposition
materialComposition: # half of autolathe printing cost
Wood: 50
Steel: 25
- type: Storage - type: Storage
grid: grid:
- 0,0,5,3 - 0,0,5,3
@@ -355,6 +367,10 @@
sprite: Objects/Misc/plastic_clipboard.rsi sprite: Objects/Misc/plastic_clipboard.rsi
- type: Clothing - type: Clothing
sprite: Objects/Misc/plastic_clipboard.rsi sprite: Objects/Misc/plastic_clipboard.rsi
- type: PhysicalComposition
materialComposition: # half of autolathe printing cost
Plastic: 50
Steel: 25
- type: entity - type: entity
parent: [BoxFolderPlasticClipboardEmpty, BoxFolderFill] parent: [BoxFolderPlasticClipboardEmpty, BoxFolderFill]
@@ -385,6 +401,11 @@
sprite: Objects/Misc/cc-clipboard.rsi sprite: Objects/Misc/cc-clipboard.rsi
- type: Clothing - type: Clothing
sprite: Objects/Misc/cc-clipboard.rsi sprite: Objects/Misc/cc-clipboard.rsi
- type: PhysicalComposition
materialComposition: # same composition as regular clipboard + bit of cloth because it's "upholstered with green velvet"
Wood: 50
Steel: 25
Cloth: 25
- type: entity - type: entity
parent: [BoxFolderCentComClipboardEmpty, BoxFolderFill] parent: [BoxFolderCentComClipboardEmpty, BoxFolderFill]

View File

@@ -362,7 +362,6 @@
- type: Tag - type: Tag
tags: tags:
- Mop - Mop
- type: CleansForensics
- type: Fiber - type: Fiber
fiberColor: fibers-white fiberColor: fibers-white
- type: DnaSubstanceTrace - type: DnaSubstanceTrace

View File

@@ -450,6 +450,23 @@
- type: BorgModuleIcon - type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module } icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module }
- type: entity
id: BorgModuleArtistry
parent: [ BaseBorgModule, BaseProviderBorgModule ]
name: artistry cyborg module
description: A module for arts & crafts whilst the station burns!
components:
- type: Sprite
layers:
- state: generic
- state: icon-artistry
- type: ItemBorgModule
hands:
- item: SprayPainterBorg
- item: CrayonBorg
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: artistry-module }
- type: entity - type: entity
id: BorgModuleFireExtinguisher id: BorgModuleFireExtinguisher
parent: [ BaseBorgModule, BaseProviderBorgModule ] parent: [ BaseBorgModule, BaseProviderBorgModule ]

View File

@@ -47,6 +47,16 @@
- type: AutoRecharge - type: AutoRecharge
rechargeDuration: 1 rechargeDuration: 1
- type: entity
parent: SprayPainter
name: experimental spray painter
description: An experimental recharging spray painter that can infinitely replicate compressed paint.
id: SprayPainterBorg
suffix: Borg
components:
- type: AutoRecharge
rechargeDuration: 5
- type: entity - type: entity
parent: SprayPainter parent: SprayPainter
id: SprayPainterEmpty id: SprayPainterEmpty

View File

@@ -16,125 +16,145 @@
- HideContextMenu - HideContextMenu
- type: AnimationPlayer - type: AnimationPlayer
- type: hitscan - type: entity
id: RedLaser id: BasicHitscan
damage: categories: [ HideSpawnMenu ]
types: components:
Heat: 14 - type: HitscanAmmo
muzzleFlash: - type: HitscanBasicRaycast
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - type: HitscanBasicVisuals
state: muzzle_laser muzzleFlash:
travelFlash: sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi state: muzzle_laser
state: beam travelFlash:
impactFlash: sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi state: beam
state: impact_laser impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_laser
- type: HitscanReflect
- type: HitscanBasicEffects
- type: hitscan - type: entity
id: RedLaserPractice parent: BasicHitscan
damage:
types:
Heat: 1
muzzleFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: muzzle_laser
travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_laser
- type: hitscan
id: RedMediumLaser
damage:
types:
Heat: 17
muzzleFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: muzzle_laser
travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_laser
- type: hitscan
id: RedLightLaser id: RedLightLaser
damage: categories: [ HideSpawnMenu ]
types: components:
Heat: 7 - type: HitscanBasicDamage
muzzleFlash: damage:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi types:
state: muzzle_laser Heat: 7
travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_laser
- type: hitscan - type: entity
id: XrayLaser parent: BasicHitscan
damage: id: RedLaser
types: categories: [ HideSpawnMenu ]
Heat: 10 components:
Radiation: 10 - type: HitscanBasicDamage
muzzleFlash: damage:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi types:
state: muzzle_xray Heat: 14
travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: xray
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_xray
- type: hitscan - type: entity
parent: BasicHitscan
id: RedMediumLaser
categories: [ HideSpawnMenu ]
components:
- type: HitscanBasicDamage
damage:
types:
Heat: 17
- type: entity
parent: BasicHitscan
id: RedHeavyLaser id: RedHeavyLaser
damage: categories: [ HideSpawnMenu ]
types: components:
Heat: 28 - type: HitscanBasicDamage
muzzleFlash: damage:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi types:
state: muzzle_beam_heavy Heat: 28
travelFlash: - type: HitscanBasicVisuals
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi muzzleFlash:
state: beam_heavy sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
impactFlash: state: muzzle_beam_heavy
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi travelFlash:
state: impact_beam_heavy sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam_heavy
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_beam_heavy
- type: hitscan - type: entity
parent: BasicHitscan
id: RedLaserPractice
categories: [ HideSpawnMenu ]
components:
- type: HitscanBasicDamage
damage:
types:
Heat: 1
- type: entity
parent: BasicHitscan
id: XrayLaser
categories: [ HideSpawnMenu ]
components:
- type: HitscanBasicDamage
damage:
types:
Heat: 10
Radiation: 10
- type: HitscanBasicVisuals
muzzleFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: muzzle_xray
travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: xray
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_xray
- type: entity
parent: BasicHitscan
id: Pulse id: Pulse
damage: categories: [ HideSpawnMenu ]
types: components:
Heat: 35 - type: HitscanBasicDamage
muzzleFlash: damage:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi types:
state: muzzle_blue Heat: 35
travelFlash: - type: HitscanBasicVisuals
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi muzzleFlash:
state: beam_blue sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
impactFlash: state: muzzle_blue
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi travelFlash:
state: impact_blue sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam_blue
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_blue
- type: hitscan - type: entity
parent: BasicHitscan
id: RedShuttleLaser id: RedShuttleLaser
maxLength: 60 categories: [ HideSpawnMenu ]
damage: components:
types: - type: HitscanBasicRaycast
Heat: 45 maxDistance: 60.0
Structural: 10 - type: HitscanBasicDamage
muzzleFlash: damage:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi types:
state: muzzle_beam_heavy2 Heat: 45
travelFlash: Structural: 10
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - type: HitscanBasicVisuals
state: beam_heavy2 muzzleFlash:
impactFlash: sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi state: muzzle_beam_heavy2
state: impact_beam_heavy2 travelFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: beam_heavy2
impactFlash:
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_beam_heavy2

View File

@@ -12,7 +12,7 @@
name: cotton bun recipe name: cotton bun recipe
result: FoodCottonBun result: FoodCottonBun
time: 5 time: 5
group: Breads group: Moth
solids: solids:
FoodDoughCottonSlice: 1 FoodDoughCottonSlice: 1
@@ -2393,6 +2393,7 @@
name: grilled cheese sandwich recipe name: grilled cheese sandwich recipe
result: FoodBakedGrilledCheeseSandwich result: FoodBakedGrilledCheeseSandwich
time: 10 time: 10
group: Savory
solids: solids:
FoodBreadPlainSlice: 2 FoodBreadPlainSlice: 2
FoodCheeseSlice: 1 FoodCheeseSlice: 1
@@ -2403,6 +2404,7 @@
name: cotton grilled cheese sandwich recipe name: cotton grilled cheese sandwich recipe
result: FoodBakedGrilledCheeseSandwichCotton result: FoodBakedGrilledCheeseSandwichCotton
time: 10 time: 10
group: Moth
solids: solids:
FoodBreadCottonSlice: 2 FoodBreadCottonSlice: 2
FoodCheeseSlice: 1 FoodCheeseSlice: 1
@@ -2453,9 +2455,8 @@
name: cotton cake recipe name: cotton cake recipe
result: FoodCakeCotton result: FoodCakeCotton
time: 5 time: 5
group: Cake group: Moth
reagents: reagents:
Fiber: 10 Fiber: 10
solids: solids:
FoodCakePlain: 1 FoodCakePlain: 1

View File

@@ -15,6 +15,7 @@
- BorgModuleCable - BorgModuleCable
- BorgModuleFireExtinguisher - BorgModuleFireExtinguisher
- BorgModuleInflatable - BorgModuleInflatable
- BorgModuleArtistry
- type: latheRecipePack - type: latheRecipePack
id: BorgLimbsStatic id: BorgLimbsStatic

View File

@@ -222,7 +222,7 @@
completetime: 2 completetime: 2
materials: materials:
Wood: 100 Wood: 100
Steel: 25 Steel: 50
- type: latheRecipe - type: latheRecipe
id: BoxFolderPlasticClipboardEmpty id: BoxFolderPlasticClipboardEmpty
@@ -230,7 +230,7 @@
completetime: 2 completetime: 2
materials: materials:
Plastic: 100 Plastic: 100
Steel: 25 Steel: 50
- type: latheRecipe - type: latheRecipe
id: TowelColorWhite id: TowelColorWhite

View File

@@ -42,6 +42,11 @@
id: BorgModuleInflatable id: BorgModuleInflatable
result: BorgModuleInflatable result: BorgModuleInflatable
- type: latheRecipe
parent: BaseBorgModuleRecipe
id: BorgModuleArtistry
result: BorgModuleArtistry
# Cargo Modules # Cargo Modules
- type: latheRecipe - type: latheRecipe

View File

@@ -19,6 +19,8 @@
- Detective - Detective
- Cryogenics - Cryogenics
- External - External
- GenpopEnter
- GenpopLeave
special: special:
- !type:AddImplantSpecial - !type:AddImplantSpecial
implants: [ MindShieldImplant ] implants: [ MindShieldImplant ]

View File

@@ -21,6 +21,8 @@
- Service - Service
- External - External
- Cryogenics - Cryogenics
- GenpopEnter
- GenpopLeave
special: special:
- !type:AddImplantSpecial - !type:AddImplantSpecial
implants: [ MindShieldImplant ] implants: [ MindShieldImplant ]

View File

@@ -18,6 +18,8 @@
- Service - Service
- External - External
- Cryogenics - Cryogenics
- GenpopEnter
- GenpopLeave
special: special:
- !type:AddImplantSpecial - !type:AddImplantSpecial
implants: [ MindShieldImplant ] implants: [ MindShieldImplant ]

View File

@@ -24,6 +24,8 @@
- External - External
- Detective - Detective
- Cryogenics - Cryogenics
- GenpopEnter
- GenpopLeave
special: special:
- !type:AddImplantSpecial - !type:AddImplantSpecial
implants: [ MindShieldImplant ] implants: [ MindShieldImplant ]

View File

@@ -230,6 +230,10 @@
path: /Audio/Voice/Vox/vox_cough.ogg path: /Audio/Voice/Vox/vox_cough.ogg
Sigh: Sigh:
path: /Audio/Voice/Vox/vox_sigh.ogg path: /Audio/Voice/Vox/vox_sigh.ogg
Click:
path: /Audio/Voice/Vox/vox_click.ogg
Chitter:
path: /Audio/Voice/Vox/vox_chitter.ogg
Honk: Honk:
collection: BikeHorn collection: BikeHorn
Crying: Crying:

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, inflatable module by FungiFellow (GitHub), Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA. c20r and esword by RedBookcase on Github.", "copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/cdbcb1e858b11f083994a7a269ed67ef5b452ce9, inflatable module by FungiFellow (GitHub), Module actions by Scarky0. chem, adv-chem, and adv-mining by mubururu_, xenoborg actions by Samuka-C (github), advclown by ThatGuyUSA. c20r and esword by RedBookcase on Github, artistry-module by Kittygyat, with help from TiniestShark",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -25,6 +25,9 @@
{ {
"name":"extinguisher-module" "name":"extinguisher-module"
}, },
{
"name":"artistry-module"
},
{ {
"name":"geiger-module" "name":"geiger-module"
}, },

View File

@@ -5,7 +5,7 @@
"y": 32 "y": 32
}, },
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/faf6db214927874c19b8fa8585d26b5d40de1acc, derelict generic sprites modified by GoldenCan(GitHub), xenoborg sprites, created and modified by Samuka-C (github). Derelict Engineer, Janitor, Miner, Medical, and Assault Borg sprites by _miket on Discord.", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/faf6db214927874c19b8fa8585d26b5d40de1acc, derelict generic sprites modified by GoldenCan(GitHub), xenoborg sprites, created and modified by Samuka-C (github), Derelict Engineer, Janitor, Miner, Medical, and Assault Borg sprites by _miket on Discord, minor resprite of the service borg by Kittygyat (github).",
"states": [ "states": [
{ {
"name": "clown", "name": "clown",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,12 +1,69 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "made by mubururu_ (github)", "copyright": "Originally from Paradise station, updated and edited for SS14 by MissKay1994 (github)",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
}, },
"states": [ "states": [
{
"name": "eyeballs-inhand-left",
"directions": 4
},
{
"name": "eyeballs-inhand-right",
"directions": 4
},
{
"name": "eyeball-l"
},
{
"name": "eyeball-r"
},
{
"name": "heart-inhand-left",
"directions": 4
},
{
"name": "heart-inhand-right",
"directions": 4
},
{
"name": "heart-on",
"delays": [
[
0.6,
0.1,
0.1
]
]
},
{
"name": "kidneys-inhand-left",
"directions": 4
},
{
"name": "kidneys-inhand-right",
"directions": 4
},
{
"name": "kidney-l"
},
{
"name": "kidney-r"
},
{
"name": "liver"
},
{
"name": "liver-inhand-left",
"directions": 4
},
{
"name": "liver-inhand-right",
"directions": 4
},
{ {
"name": "lungs-inhand-left", "name": "lungs-inhand-left",
"directions": 4 "directions": 4
@@ -20,6 +77,20 @@
}, },
{ {
"name": "lung-r" "name": "lung-r"
},
{
"name": "stomach"
},
{
"name": "stomach-inhand-left",
"directions": 4
},
{
"name": "stomach-inhand-right",
"directions": 4
},
{
"name": "tongue"
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation and modified by Swept at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24, tweaked by Ubaser", "copyright": "Taken from tgstation and modified by Swept at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24, tweaked by Ubaser, electric crayon by Kittygyat",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -40,6 +40,9 @@
"name": "box-inhand-right", "name": "box-inhand-right",
"directions": 4 "directions": 4
}, },
{
"name": "electric"
},
{ {
"name": "green" "name": "green"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC0-1.0", "license": "CC0-1.0",
"copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github). syndicateborgbomb.png created by Mangohydra (github). icon-chem.png & icon-mining-adv.png created by mubururu_ (github) icon-inflatable.png made by FungiFellow (GitHub), Xenoborg modules sprites by Samuka-C (github)", "copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github). syndicateborgbomb.png created by Mangohydra (github). icon-chem.png & icon-mining-adv.png created by mubururu_ (github) icon-inflatable.png made by FungiFellow (GitHub), Xenoborg modules sprites by Samuka-C (github), icon-artistry by Kittygyat",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -55,6 +55,9 @@
{ {
"name": "icon-fire-extinguisher" "name": "icon-fire-extinguisher"
}, },
{
"name": "icon-artistry"
},
{ {
"name": "icon-gardening" "name": "icon-gardening"
}, },