diff --git a/Content.Shared/BarricadeBlock/BarricadeBlockComponent.cs b/Content.Shared/BarricadeBlock/BarricadeBlockComponent.cs
new file mode 100644
index 00000000000..8ef7ec4305a
--- /dev/null
+++ b/Content.Shared/BarricadeBlock/BarricadeBlockComponent.cs
@@ -0,0 +1,38 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+using System.Collections.Generic;
+using Content.Shared.Tag;
+
+namespace Content.Shared.BarricadeBlock;
+
+[RegisterComponent]
+public sealed partial class BarricadeBlockComponent : Component
+{
+ ///
+ /// % chance of blocking a projectile passing overhead
+ ///
+ [DataField("blocking")]
+ public int Blocking = 66;
+
+ ///
+ /// Can it be used bidirectionally (e.g. sandbags) or only from behind (e.g. crenelated walls)?
+ ///
+ [DataField("bidirectional")]
+ public bool Bidirectional = true;
+
+ ///
+ /// Can it be used omnidirectionally (e.g. vending machines)
+ ///
+ [DataField("omnidirectional")]
+ public bool Omnidirectional = true;
+
+ ///
+ /// Distance between the shooter and barricade
+ ///
+ [DataField("passthroughdistance")]
+ public float PassThroughDistance = 1.5f;
+
+}
+
+/// ported from civ14
diff --git a/Content.Shared/CCVar/CCVars.Movement.cs b/Content.Shared/CCVar/CCVars.Movement.cs
index 8bfbdcad608..ac39ec645ca 100644
--- a/Content.Shared/CCVar/CCVars.Movement.cs
+++ b/Content.Shared/CCVar/CCVars.Movement.cs
@@ -11,7 +11,7 @@ public sealed partial class CCVars
///
[CVarControl(AdminFlags.VarEdit)]
public static readonly CVarDef MovementMobPushing =
- CVarDef.Create("movement.mob_pushing", false, CVar.SERVER | CVar.REPLICATED);
+ CVarDef.Create("movement.mob_pushing", true, CVar.SERVER | CVar.REPLICATED); // LuaM false -> true
///
/// Can we push mobs not moving.
diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs
index dfa03bed17a..718a1122c78 100644
--- a/Content.Shared/Projectiles/SharedProjectileSystem.cs
+++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs
@@ -36,13 +36,14 @@
using Robust.Shared.Configuration;
using Content.Shared._Mono.CCVar;
using Robust.Shared;
+using Content.Shared.BarricadeBlock; // BF14
+using Robust.Shared.Random; // BF14
namespace Content.Shared.Projectiles;
public abstract partial class SharedProjectileSystem : EntitySystem
{
public const string ProjectileFixture = "projectile";
-
[Dependency] private ISharedAdminLogManager _adminLogger = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedColorFlashEffectSystem _color = default!;
@@ -58,6 +59,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private INetManager _net = default!;
[Dependency] private IConfigurationManager _cfg = default!; // Mono
+ [Dependency] private IRobustRandom _random = default!; // BF14
// Cache of projectiles waiting for collision checks
private readonly ConcurrentQueue<(EntityUid Uid, ProjectileComponent Component, EntityUid Target)> _pendingCollisionChecks = new();
@@ -377,6 +379,85 @@ private void OnEmbedRemove(Entity embeddable, ref
_hands.TryPickupAnyHand(args.User, embeddable);
}
+ //ported from civ14
+ private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
+ {
+ if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
+ {
+ args.Cancelled = true;
+ }
+ //check for BarricadeBlock component (percentage of chance to hit/pass over)
+ if (TryComp(args.OtherEntity, out BarricadeBlockComponent? BarricadeBlock))
+ {
+ var alwaysPassThrough = false;
+ //_sawmill.Info("Checking BarricadeBlock...");
+ if (component.Shooter is { } shooterUid && Exists(shooterUid))
+ {
+ // Condition 1: Directions are the same (using cardinal directions).
+ // Or, if bidirectional, directions can be opposite.
+ var shooterWorldRotation = _transform.GetWorldRotation(shooterUid);
+ var BarricadeBlockWorldRotation = _transform.GetWorldRotation(args.OtherEntity);
+
+ var shooterDir = shooterWorldRotation.GetCardinalDir();
+ var BarricadeBlockDir = BarricadeBlockWorldRotation.GetCardinalDir();
+
+ bool directionallyAllowed = false;
+ if (shooterDir == BarricadeBlockDir)
+ {
+ directionallyAllowed = true;
+ //_sawmill.Debug("Shooter and BarricadeBlock facing same cardinal direction.");
+ }
+ else if (BarricadeBlock.Bidirectional)
+ {
+ var oppositeBarricadeBlockDir = (Direction)(((int)BarricadeBlockDir + 4) % 8);
+ if (shooterDir == oppositeBarricadeBlockDir)
+ {
+ directionallyAllowed = true;
+ //_sawmill.Debug("Shooter and BarricadeBlock facing opposite cardinal directions (bidirectional pass).");
+ }
+ }
+ else if (BarricadeBlock.Omnidirectional)
+ {
+ directionallyAllowed = true;
+ //_sawmill.Debug("Has the omnidirectional field");
+ }
+
+ if (directionallyAllowed)
+ {
+ // Condition 2: Firer is within 1 tile of the BarricadeBlock.
+ var shooterCoords = Transform(shooterUid).Coordinates;
+ var BarricadeBlockCoords = Transform(args.OtherEntity).Coordinates;
+ var BypassDistance = BarricadeBlock.PassThroughDistance;
+
+ if (shooterCoords.TryDistance(EntityManager, BarricadeBlockCoords, out var distance) &&
+ distance <= BypassDistance)
+ {
+ alwaysPassThrough = true;
+ }
+ }
+ }
+
+ if (alwaysPassThrough)
+ {
+ args.Cancelled = true;
+ }
+ else
+ {
+ //_sawmill.Debug("BarricadeBlock direction/distance check failed or shooter not valid.");
+ // Standard BarricadeBlock blocking logic if the special conditions are not met.
+ var rando = _random.NextFloat(0.0f, 100.0f);
+ if (rando >= BarricadeBlock.Blocking)
+ {
+ args.Cancelled = true;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+ }
+
private void OnEmbedThrowDoHit(Entity embeddable, ref ThrowDoHitEvent args)
{
if (!embeddable.Comp.EmbedOnThrow)
@@ -487,58 +568,6 @@ public void DetachAllEmbedded(Entity container)
}
}
- private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
- {
- // Goobstation - Crawling fix
- if (TryComp(args.OtherEntity, out var requireTarget) && requireTarget.IgnoreThrow && requireTarget.Active)
- return;
-
- if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
- {
- args.Cancelled = true;
- return;
- }
-
- // Get transforms once for subsequent checks to avoid repeated calls
- var projectileXform = Transform(uid);
- var targetXform = Transform(args.OtherEntity);
-
- // Add collision check to queue for batch processing if we have enough
- if (_pendingCollisionChecks.Count >= MinProjectilesForParallel / 2)
- {
- _pendingCollisionChecks.Enqueue((uid, component, args.OtherEntity));
-
- // Assume collision for now - if shield check passes, we'll handle it in the batch process
- return;
- }
-
- // For low volume, process immediately
- // Check if any shield system wants to prevent collision
- var ev = new ProjectileCollisionAttemptEvent(uid, args.OtherEntity);
- RaiseLocalEvent(ref ev);
-
- if (ev.Cancelled)
- {
- args.Cancelled = true;
- return;
- }
-
- // Check if target and projectile are on different maps/z-levels
- if (projectileXform.MapID != targetXform.MapID)
- {
- args.Cancelled = true;
- return;
- }
-
- // Define the tag constant
- const string GunCanAimShooterTag = "GunCanAimShooter";
-
- if ((component.Shooter == args.OtherEntity || component.Weapon == args.OtherEntity) &&
- component.Weapon != null && _tag.HasTag(component.Weapon.Value, GunCanAimShooterTag) &&
- TryComp(uid, out TargetedProjectileComponent? targeted) && targeted.Target == args.OtherEntity)
- return;
- }
-
// Goobstation - Crawling fix
private void EmbeddablePreventCollision(EntityUid uid, EmbeddableProjectileComponent component, ref PreventCollideEvent args)
{
diff --git a/Content.Shared/Traits/Assorted/ExtendDescriptionComponent.cs b/Content.Shared/Traits/Assorted/ExtendDescriptionComponent.cs
new file mode 100644
index 00000000000..58d1fe621a2
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/ExtendDescriptionComponent.cs
@@ -0,0 +1,36 @@
+//using Content.Shared.Customization.Systems;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Traits.Assorted.Components;
+
+[Serializable, NetSerializable, DataDefinition]
+public sealed partial class DescriptionExtension
+{
+// [DataField]
+// public List? Requirements;
+
+ [DataField]
+ public string Description = "";
+
+// [DataField]
+// public string RequirementsNotMetDescription = "";
+
+ [DataField]
+ public int FontSize = 12;
+
+ [DataField]
+ public string Color = "#ffffff";
+
+ [DataField]
+ public bool RequireDetailRange = true;
+}
+
+[RegisterComponent]
+public sealed partial class ExtendDescriptionComponent : Component
+{
+ ///
+ /// The list of all descriptions to add to an entity when examined at close range.
+ ///
+ [DataField]
+ public List DescriptionList = new();
+}
diff --git a/Content.Shared/Traits/Assorted/ExtendDescriptionSystem.cs b/Content.Shared/Traits/Assorted/ExtendDescriptionSystem.cs
new file mode 100644
index 00000000000..ba46fcca099
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/ExtendDescriptionSystem.cs
@@ -0,0 +1,30 @@
+//using Content.Shared.Customization.Systems;
+using Content.Shared.Examine;
+using Content.Shared.Traits.Assorted.Components;
+
+namespace Content.Shared.Traits.Assorted.Systems;
+
+public sealed class ExtendDescriptionSystem : EntitySystem
+{
+// [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnExamined);
+ }
+
+ private void OnExamined(EntityUid uid, ExtendDescriptionComponent component, ExaminedEvent args)
+ {
+ if (component.DescriptionList.Count <= 0)
+ return;
+
+ foreach (var desc in component.DescriptionList)
+ {
+ if (!args.IsInDetailsRange && desc.RequireDetailRange)
+ continue;
+
+ args.PushMarkup($"[font size ={desc.FontSize}][color={desc.Color}]{Loc.GetString(desc.Description, ("entity", uid))}[/color][/font]");
+ }
+ }
+}
diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml
index 7bc23ace817..eded3d85ccc 100644
--- a/Resources/Prototypes/Entities/Mobs/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/base.yml
@@ -52,6 +52,16 @@
- type: PassiveThermalSignature # Mono
signature: 225 # if nothing else is generating heat, 30m radius, 60m with 4 mobs, 120 with 16
- type: ThermalSignature # Mono
+ - type: BarricadeBlock # Mono
+ bidirectional: false
+ omnidirectional: true
+ passthroughdistance: 1
+ blocking: 100
+ - type: ExtendDescription # BF14
+ descriptionList:
+ - description: Firing a weapon within half a tile will fire over their shoulder.
+ color: "#4f94ad"
+ requireDetailRange: true
- type: entity
save: false
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
index 3fa7c465bf6..40d23ae2535 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
@@ -85,7 +85,7 @@
projectile:
shape:
!type:PhysShapeAabb
- bounds: "-0.1,-0.1,0.1,0.1"
+ bounds: "-0.1,-0.1,0.1,0.3"
hard: false
mask:
- Impassable
diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
index b2808ee0e87..f04e9e38e24 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
@@ -141,6 +141,9 @@
- type: ShipRepairable
repairTime: 5
repairCost: 10
+ - type: BarricadeBlock
+ bidirectional: true
+ blocking: 67
- type: entity
parent: VendingMachine
diff --git a/Resources/Prototypes/_Goobstation/Damage/modifier_sets.yml b/Resources/Prototypes/_Goobstation/Damage/modifier_sets.yml
index 01bf6809eca..83990c326de 100644
--- a/Resources/Prototypes/_Goobstation/Damage/modifier_sets.yml
+++ b/Resources/Prototypes/_Goobstation/Damage/modifier_sets.yml
@@ -92,3 +92,60 @@
Piercing: 1.15
Heat: 1.20
Cold: 0.80
+
+# Barricades
+
+- type: damageModifierSet
+ id: BarricadeSteel
+ coefficients:
+ Blunt: 0.8
+ Slash: 0.7
+ Piercing: 0.5
+ flatReductions:
+ Blunt: 5
+ Slash: 5
+ Piercing: 5
+ Heat: 5
+ Structural: 5
+
+- type: damageModifierSet
+ id: BarricadeSteelFolding
+ coefficients:
+ Blunt: 0.9
+ Slash: 0.9
+ Piercing: 0.65
+ flatReductions:
+ Blunt: 5
+ Slash: 5
+ Piercing: 5
+ Heat: 3
+
+- type: damageModifierSet
+ id: BarricadePlasteel
+ coefficients:
+ Blunt: 0.6
+ Slash: 0.5
+ Piercing: 0.3
+ Structural: 0.75
+ Heat: 0.5
+ flatReductions:
+ Blunt: 5
+ Slash: 5
+ Piercing: 5
+ Heat: 3
+ Structural: 5
+
+- type: damageModifierSet
+ id: BarricadePlasteelFolding
+ coefficients:
+ Blunt: 0.8
+ Slash: 0.7
+ Piercing: 0.8
+ Structural: 0.85
+ Heat: 0.5
+ flatReductions:
+ Blunt: 5
+ Slash: 5
+ Piercing: 5
+ Heat: 3
+ Structural: 3