瀏覽代碼

QOL patch 1 (#229)

* buffs walls, reverts bug

* fixes setting presets

* job icons, research and mortar tweak

* 📝 Add docstrings to `qol_patch_1` (#230)

Docstrings generation was requested by @taislin.

* https://github.com/Civ13/Civ14/pull/229#issuecomment-2908241025

The following files were modified:

* `Content.Client/Overlays/ShowFactionIconsSystem.cs`
* `Content.Server/GameTicking/GameTicker.GameRule.cs`
* `Content.Server/GameTicking/Rules/RandomWeatherRuleSystem.cs`
* `Content.Shared/Inventory/InventorySystem.Relay.cs`

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix sandals

* squad icons and rank icons

* armor tweaks

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Taislin 6 月之前
父節點
當前提交
14c538a4d1
共有 38 個文件被更改,包括 990 次插入1022 次删除
  1. 20 105
      Content.Client/Overlays/ShowFactionIconsSystem.cs
  2. 6 1
      Content.Server/GameTicking/GameTicker.GameRule.cs
  3. 5 0
      Content.Server/GameTicking/Rules/Components/RandomWeatherRuleComponent.cs
  4. 10 1
      Content.Server/GameTicking/Rules/RandomWeatherRuleSystem.cs
  5. 34 0
      Content.Server/GameTicking/Rules/TeamDeathMatchRuleSystem.cs
  6. 6 0
      Content.Server/Maps/GameMapPrototype.cs
  7. 235 0
      Content.Server/Overlays/ShowFactionIconsSystem.cs
  8. 71 6
      Content.Server/StationRecords/Systems/StationRecordsSystem.cs
  9. 2 2
      Content.Shared/Civ14/CivResearch/CivResearchComponent.cs
  10. 66 0
      Content.Shared/Civ14/CivTDMFactions/CivTDMFactionsComponent.cs
  11. 3 5
      Content.Shared/Inventory/InventorySystem.Relay.cs
  12. 1 1
      Content.Shared/NPC/Systems/NpcFactionSystem.cs
  13. 21 41
      Content.Shared/Overlays/ShowFactionIconsComponent.cs
  14. 131 0
      Content.Shared/Overlays/ShowFactionIconsSystem.cs
  15. 1 1
      Content.Shared/_RMC14/Mortar/MortarComponent.cs
  16. 1 1
      LICENSE.TXT
  17. 3 0
      Resources/Maps/civ/tdm/camp_ww2.yml
  18. 3 0
      Resources/Maps/civ/tdm/hotel.yml
  19. 3 0
      Resources/Maps/civ/tdm/opushka.yml
  20. 62 526
      Resources/Prototypes/Civ14/Entities/Clothing/entities_clothing_accessories.yml
  21. 48 184
      Resources/Prototypes/Civ14/Entities/Clothing/entities_clothing_shoes.yml
  22. 18 18
      Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortar_shells.yml
  23. 53 55
      Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortars.yml
  24. 6 0
      Resources/Prototypes/Civ14/Entities/Structures/Walls/walls.yml
  25. 0 15
      Resources/Prototypes/Civ14/StatusIcon/faction.yml
  26. 74 18
      Resources/Prototypes/Civ14/StatusIcon/job.yml
  27. 0 3
      Resources/Prototypes/Entities/Structures/Furniture/beds.yml
  28. 4 1
      Resources/Prototypes/Entities/Structures/Walls/walls.yml
  29. 6 6
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/english.yml
  30. 6 6
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/french.yml
  31. 20 8
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/german.yml
  32. 20 8
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/soviet.yml
  33. 15 5
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/sovietCW.yml
  34. 13 5
      Resources/Prototypes/Roles/Jobs/Civ14/TDM/usa.yml
  35. 23 0
      Resources/Textures/Civ14/Objects/medals.rsi/meta.json
  36. 二進制
      Resources/Textures/Civ14/Objects/medals.rsi/nomads_bronze.png
  37. 二進制
      Resources/Textures/Civ14/Objects/medals.rsi/nomads_gold.png
  38. 二進制
      Resources/Textures/Civ14/Objects/medals.rsi/nomads_silver.png

+ 20 - 105
Content.Client/Overlays/ShowFactionIconsSystem.cs

@@ -1,8 +1,4 @@
-using Content.Shared.Access.Components;
-using Content.Shared.Access.Systems;
-using Content.Shared.NPC.Components;
 using Content.Shared.Overlays;
-using System.Linq;
 using Content.Shared.StatusIcon;
 using Content.Shared.StatusIcon.Components;
 using Robust.Shared.Prototypes;
@@ -13,6 +9,7 @@ public sealed class ShowFactionIconsSystem : EquipmentHudSystem<ShowFactionIcons
 {
     [Dependency] private readonly IPrototypeManager _prototype = default!;
 
+
     public override void Initialize()
     {
         base.Initialize();
@@ -21,114 +18,32 @@ public override void Initialize()
 
     }
 
+    /// <summary>
+    /// Adds faction and job icons to the status icon list for an entity if their prototypes are found.
+    /// </summary>
+    /// <param name="uid">The entity requesting status icons.</param>
+    /// <param name="component">The component specifying faction and job icon identifiers.</param>
+    /// <param name="ev">The event containing the status icon list to update.</param>
     private void OnGetStatusIconsEvent(EntityUid uid, ShowFactionIconsComponent component, ref GetStatusIconsEvent ev)
     {
         if (!IsActive)
             return;
 
+        // Display regular faction icon
         if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
             ev.StatusIcons.Add(iconPrototype);
-    }
-}
-
-public sealed class ShowFrenchFactionIconsSystem : EquipmentHudSystem<ShowFrenchFactionIconsComponent>
-{
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ShowFrenchFactionIconsComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
-
-    }
-
-    private void OnGetStatusIconsEvent(EntityUid uid, ShowFrenchFactionIconsComponent component, ref GetStatusIconsEvent ev)
-    {
-        if (!IsActive)
-            return;
-
-        if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
-            ev.StatusIcons.Add(iconPrototype);
-    }
-}
-public sealed class ShowEnglishFactionIconsSystem : EquipmentHudSystem<ShowEnglishFactionIconsComponent>
-{
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ShowEnglishFactionIconsComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
-
-    }
-
-    private void OnGetStatusIconsEvent(EntityUid uid, ShowEnglishFactionIconsComponent component, ref GetStatusIconsEvent ev)
-    {
-        if (!IsActive)
-            return;
 
-        if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
-            ev.StatusIcons.Add(iconPrototype);
+        // Display squad-specific icon if assigned by the server
+        if (component.AssignSquad && component.SquadIcon != null)
+        {
+            if (_prototype.TryIndex<JobIconPrototype>(component.SquadIcon, out var squadIconPrototype))
+                ev.StatusIcons.Add(squadIconPrototype);
+        }
+        // Otherwise, display the general job icon if no squad icon is present or if not part of a squad
+        if (component.JobIcon != null && component.JobIcon != "JobIconSoldier" && component.JobIcon != "JobIconRifleman" && component.JobIcon != "JobIconMG")
+        {
+            if (_prototype.TryIndex<JobIconPrototype>(component.JobIcon, out var jobIconPrototype))
+                ev.StatusIcons.Add(jobIconPrototype);
+        }
     }
 }
-public sealed class ShowGermanFactionIconsSystem : EquipmentHudSystem<ShowGermanFactionIconsComponent>
-{
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ShowGermanFactionIconsComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
-
-    }
-
-    private void OnGetStatusIconsEvent(EntityUid uid, ShowGermanFactionIconsComponent component, ref GetStatusIconsEvent ev)
-    {
-        if (!IsActive)
-            return;
-
-        if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
-            ev.StatusIcons.Add(iconPrototype);
-    }
-}
-public sealed class ShowSovietFactionIconsSystem : EquipmentHudSystem<ShowSovietFactionIconsComponent>
-{
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ShowSovietFactionIconsComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
-
-    }
-
-    private void OnGetStatusIconsEvent(EntityUid uid, ShowSovietFactionIconsComponent component, ref GetStatusIconsEvent ev)
-    {
-        if (!IsActive)
-            return;
-
-        if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
-            ev.StatusIcons.Add(iconPrototype);
-    }
-}
-public sealed class ShowUsFactionIconsSystem : EquipmentHudSystem<ShowUsFactionIconsComponent>
-{
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ShowUsFactionIconsComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
-
-    }
-
-    private void OnGetStatusIconsEvent(EntityUid uid, ShowUsFactionIconsComponent component, ref GetStatusIconsEvent ev)
-    {
-        if (!IsActive)
-            return;
-
-        if (_prototype.TryIndex<FactionIconPrototype>(component.FactionIcon, out var iconPrototype))
-            ev.StatusIcons.Add(iconPrototype);
-    }
-}
-

+ 6 - 1
Content.Server/GameTicking/GameTicker.GameRule.cs

@@ -313,15 +313,20 @@ public IEnumerable<EntityPrototype> GetAllGameRulePrototypes()
         }
     }
 
+    /// <summary>
+    /// Sets the game preset for the selected map if required, or starts any delayed game rules whose start time has elapsed.
+    /// </summary>
     private void UpdateGameRules()
     {
 
         if (_gameMapManager.GetSelectedMap() is { } mapPrototype)
         {
             var map = mapPrototype;
-            if (map.FixedPreset != "")
+            if (map.FixedPreset != "" && map.FixedPresetInitialised == false)
             {
+                _sawmill.Info("Set game preset to " + map.FixedPreset);
                 SetGamePreset(map.FixedPreset);
+                map.FixedPresetInitialised = true;
             }
         }
         else

+ 5 - 0
Content.Server/GameTicking/Rules/Components/RandomWeatherRuleComponent.cs

@@ -23,7 +23,12 @@ public sealed partial class RandomWeatherRuleComponent : Component
     /// <summary>
     /// List of pre-set colors, mostly for tdm maps so we can set fixed times of day.
     /// </summary>
+    [DataField("weatherInitialised")]
+    public bool WeatherInitialised = false;
 
+    /// <summary>
+    /// List of pre-set colors, mostly for tdm maps so we can set fixed times of day.
+    /// </summary>
     [DataField("dayTimes")]
     public List<string> DayTimes = [
         "Day", //Daylight #D8B059

+ 10 - 1
Content.Server/GameTicking/Rules/RandomWeatherRuleSystem.cs

@@ -42,7 +42,12 @@ protected override void Started(EntityUid uid, RandomWeatherRuleComponent compon
 
     /// <summary>
     /// Picks a random weather from the component's AllowedWeathers list and sets it as the CurrentWeather.
+    /// <summary>
+    /// Selects and applies a random weather type from the allowed list to all maps, setting the weather for one hour if it has not already been initialised.
     /// </summary>
+    /// <remarks>
+    /// If the allowed weather list is empty, no weather is set. If the selected weather is not recognised, the operation is aborted. Weather is only set once per rule activation.
+    /// </remarks>
     public void PickRandomWeather(EntityUid uid, RandomWeatherRuleComponent? component = null)
     {
         if (!Resolve(uid, ref component))
@@ -70,7 +75,11 @@ public void PickRandomWeather(EntityUid uid, RandomWeatherRuleComponent? compone
         }
         foreach (var mapId in _mapManager.GetAllMapIds())
         {
-            _weather.SetWeather(mapId, weather, endTime);
+            if (component.WeatherInitialised == false)
+            {
+                _weather.SetWeather(mapId, weather, endTime);
+                component.WeatherInitialised = true;
+            }
         }
     }
 

+ 34 - 0
Content.Server/GameTicking/Rules/TeamDeathMatchRuleSystem.cs

@@ -17,6 +17,9 @@
 using Robust.Shared.Utility;
 using Content.Shared.NPC.Systems;
 using Robust.Shared.Player;
+using Content.Server.Overlays; // Added for FactionIconsSystem
+using Content.Shared.Overlays;
+using Content.Shared.Civ14.CivTDMFactions; // Added for CivTDMFactionsComponent
 
 namespace Content.Server.GameTicking.Rules;
 
@@ -34,6 +37,7 @@ public sealed class TeamDeathMatchRuleSystem : GameRuleSystem<TeamDeathMatchRule
     [Dependency] private readonly NpcFactionSystem _factionSystem = default!; // Added dependency
     [Dependency] private readonly TransformSystem _transform = default!;
     [Dependency] private readonly IEntityManager _entities = default!;
+    [Dependency] private readonly FactionIconsSystem _factionIconsSystem = default!;
     public override void Initialize()
     {
         base.Initialize();
@@ -105,27 +109,57 @@ private void OnKillReported(ref KillReportedEvent ev)
             if (!GameTicker.IsGameRuleActive(uid, rule))
                 continue;
 
+            bool killedPlayerWasInSquad = false;
+
             // Check if the killed entity is part of either team using FactionSystem
             if (HasComp<NpcFactionMemberComponent>(ev.Entity))
             {
                 string killedTeam = "";
+                ShowFactionIconsComponent? factIcons = null; // Resolve component once
+                TryComp<ShowFactionIconsComponent>(ev.Entity, out factIcons);
 
                 if (_factionSystem.IsMember(ev.Entity, dm.Team1))
                 {
                     dm.Team1Deaths += 1;
                     dm.Team2Kills += 1;
                     killedTeam = dm.Team1;
+                    if (factIcons != null && factIcons.AssignedSquadNameKey != null)
+                    {
+                        killedPlayerWasInSquad = true;
+                    }
                 }
                 else if (_factionSystem.IsMember(ev.Entity, dm.Team2))
                 {
                     dm.Team2Deaths += 1;
                     dm.Team1Kills += 1;
                     killedTeam = dm.Team2;
+                    if (factIcons != null && factIcons.AssignedSquadNameKey != null)
+                    {
+                        killedPlayerWasInSquad = true;
+                    }
+                }
+
+                if (killedPlayerWasInSquad)
+                {
+                    CivTDMFactionsComponent? civTDMComp = null;
+                    // Attempt to find the CivTDMFactionsComponent.
+                    // This query assumes it's a somewhat global component (e.g., on a map or game rule entity).
+                    var civQuery = _entities.EntityQueryEnumerator<CivTDMFactionsComponent>();
+                    if (civQuery.MoveNext(out _, out civTDMComp)) // Use the first one found
+                    {
+                        _factionIconsSystem.RecalculateAllCivFactionSquadCounts(civTDMComp);
+                        Log.Debug($"Player {ToPrettyString(ev.Entity)} died while in squad {factIcons?.AssignedSquadNameKey}. Recalculating CivTDMFaction squad counts.");
+                    }
+                    else
+                    {
+                        Log.Warning($"Player {ToPrettyString(ev.Entity)} died in a squad, but CivTDMFactionsComponent was not found to update counts.");
+                    }
                 }
 
                 // Track individual player stats
                 if (ev.Primary is KillPlayerSource playerSource)
                 {
+
                     var playerIdStr = playerSource.PlayerId.ToString();
 
                     if (!dm.KDRatio.ContainsKey(playerIdStr))

+ 6 - 0
Content.Server/Maps/GameMapPrototype.cs

@@ -50,6 +50,12 @@ public sealed partial class GameMapPrototype : IPrototype
     [DataField("fixedPreset")]
     public string FixedPreset { get; private set; } = "";
 
+    /// <summary>
+    /// To prevent looping
+    /// </summary>
+    [DataField("fixedPresetInitialised")]
+    public bool FixedPresetInitialised { get; set; } = false;
+
     [DataField("stations", required: true)]
     private Dictionary<string, StationConfig> _stations = new();
 

+ 235 - 0
Content.Server/Overlays/ShowFactionIconsSystem.cs

@@ -0,0 +1,235 @@
+using Content.Shared.Overlays;
+using Robust.Shared.Player;
+using Content.Shared.Civ14.CivTDMFactions;
+using Robust.Shared.Random; // Added for IRobustRandom
+using System.Linq;
+using Content.Shared.NPC.Components;         // Added for LINQ
+
+namespace Content.Server.Overlays
+{
+    /// <summary>
+    /// Server-side system for managing faction and squad icon assignments.
+    /// Inherits core logic from SharedFactionIconsSystem.
+    /// </summary>
+    public sealed class FactionIconsSystem : SharedFactionIconsSystem
+    {
+        [Dependency] private readonly IRobustRandom _random = default!;
+        [Dependency] private readonly EntityManager _entityManager = default!;
+
+        public override void Initialize()
+        {
+            base.Initialize();
+        }
+
+        /// <summary>
+        /// Attempts to assign a player entity to a squad within a specific CivFaction,
+        /// balancing members and managing sergeant roles per CivFaction.
+        /// This can be called by other server systems (e.g., role assignment, admin commands).
+        /// </summary>
+        /// <param name="playerUid">The entity UID of the player.</param>
+        /// <param name="playerAssignedCivFactionId">The ID of the CivFaction the player belongs to (e.g., Faction1Id from CivTDMFactionsComponent).</param>
+        /// <param name="wantsToBeSergeant">Whether the player desires a sergeant role.</param>
+        /// <param name="sfiComponent">The player's ShowFactionIconsComponent.</param>
+        /// <returns>True if successfully assigned, false otherwise.</returns>
+        public bool AttemptAssignPlayerToSquad(EntityUid playerUid, string playerAssignedCivFactionId, bool wantsToBeSergeant, ShowFactionIconsComponent? sfiComponent = null)
+        {
+            if (!Resolve(playerUid, ref sfiComponent, logMissing: false))
+            {
+                Log.Warning($"Player {ToPrettyString(playerUid)} does not have ShowFactionIconsComponent. Cannot assign to squad.");
+                return false;
+            }
+            if (TryComp<ShowFactionIconsComponent>(playerUid, out var factMem))
+            {
+                if (factMem.AssignSquad == false)
+                {
+                    return false;
+                }
+            }
+            // Get the CivTDMFactionsComponent
+            CivTDMFactionsComponent? civTDMComp = null;
+            var civQuery = _entityManager.EntityQueryEnumerator<CivTDMFactionsComponent>();
+            if (civQuery.MoveNext(out _, out civTDMComp))
+            {
+                // Ensure Faction IDs are set
+                if (civTDMComp.Faction1Id == null || civTDMComp.Faction2Id == null)
+                {
+                    Log.Error("CivTDMFactionsComponent Faction1Id or Faction2Id is not set. Cannot assign squads.");
+                    return false;
+                }
+            }
+            else
+            {
+                Log.Error("CivTDMFactionsComponent not found. Cannot assign squads.");
+                return false;
+            }
+
+            string? targetSquadNameKey = null; // e.g., "Alpha", "Bravo"
+            bool assignAsSergeantInCall = wantsToBeSergeant;
+            string targetCivFactionIdForAssignment = playerAssignedCivFactionId;
+
+            if (wantsToBeSergeant)
+            {
+                var squadsInTargetCivFaction = GetCivFactionSquads(civTDMComp, targetCivFactionIdForAssignment);
+                targetSquadNameKey = FindBestSquadForRole(squadsInTargetCivFaction, true);
+
+                if (targetSquadNameKey == null) // No suitable squad for sergeant in target CivFaction
+                {
+                    assignAsSergeantInCall = false; // Try to assign as member in their original faction
+                    targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Revert to player's own faction
+                }
+            }
+
+            if (!assignAsSergeantInCall) // Assigning as member (either initially or fallback)
+            {
+                targetCivFactionIdForAssignment = playerAssignedCivFactionId; // Ensure assignment is to player's own faction
+                var squadsInPlayerCivFaction = GetCivFactionSquads(civTDMComp, playerAssignedCivFactionId);
+                targetSquadNameKey = FindBestSquadForRole(squadsInPlayerCivFaction, false);
+            }
+
+            if (targetSquadNameKey == null)
+            {
+                Log.Info($"Could not find a suitable squad for {ToPrettyString(playerUid)} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}.");
+                return false;
+            }
+
+            // Store old state for count updates
+            var oldSquadIcon = sfiComponent.SquadIcon;
+            var oldBelongsToCivFaction = sfiComponent.BelongsToCivFactionId;
+
+            bool success = base.TryAssignToSquad(playerUid, targetSquadNameKey, assignAsSergeantInCall, sfiComponent);
+
+            if (success)
+            {
+                sfiComponent.BelongsToCivFactionId = targetCivFactionIdForAssignment; // Update player's CivFaction if it changed
+                Dirty(playerUid, sfiComponent);
+                RecalculateAllCivFactionSquadCounts(civTDMComp); // Recalculate counts after assignment
+                Log.Info($"Successfully assigned {ToPrettyString(playerUid)} to squad {targetSquadNameKey} in CivFaction {targetCivFactionIdForAssignment} as {(assignAsSergeantInCall ? "Sergeant" : "Member")}. New icon: {sfiComponent.SquadIcon}");
+            }
+            else
+            {
+                Log.Info($"Failed to assign {ToPrettyString(playerUid)} to squad {targetSquadNameKey} via SharedFactionIconsSystem.");
+                // Revert BelongsToCivFactionId if it was tentatively changed for sergeant assignment
+                if (wantsToBeSergeant && targetCivFactionIdForAssignment != playerAssignedCivFactionId)
+                {
+                    sfiComponent.BelongsToCivFactionId = playerAssignedCivFactionId;
+                }
+            }
+            return success;
+        }
+
+        private Dictionary<string, int> CountSergeantsPerCivFaction(CivTDMFactionsComponent civTDMComp)
+        {
+            var counts = new Dictionary<string, int>();
+            if (civTDMComp.Faction1Id != null) counts[civTDMComp.Faction1Id] = 0;
+            if (civTDMComp.Faction2Id != null) counts[civTDMComp.Faction2Id] = 0;
+
+            var query = _entityManager.EntityQueryEnumerator<ShowFactionIconsComponent>();
+            while (query.MoveNext(out _, out var sfiComp))
+            {
+                if (sfiComp.BelongsToCivFactionId != null &&
+                    sfiComp.AssignSquad && // Must be assigned to a squad
+                    sfiComp.IsSergeantInSquad && // Must be a sergeant
+                    counts.ContainsKey(sfiComp.BelongsToCivFactionId))
+                {
+                    counts[sfiComp.BelongsToCivFactionId]++;
+                }
+            }
+            return counts;
+        }
+
+        private Dictionary<string, SquadData>? GetCivFactionSquads(CivTDMFactionsComponent civTDMComp, string civFactionId)
+        {
+            if (civFactionId == civTDMComp.Faction1Id)
+                return civTDMComp.Faction1Squads;
+            if (civFactionId == civTDMComp.Faction2Id)
+                return civTDMComp.Faction2Squads;
+            return null;
+        }
+
+        private string? FindBestSquadForRole(Dictionary<string, SquadData>? squadsData, bool findForSergeant)
+        {
+            if (squadsData == null)
+                return null;
+
+            string? bestSquad = null;
+            int minMembers = int.MaxValue;
+
+            foreach (var (squadNameKey, squadData) in squadsData.OrderBy(x => _random.Next())) // Randomize tie-breaking
+            {
+                if (!Squads.TryGetValue(squadNameKey, out var squadConfig))
+                    continue; // This squad type isn't defined in SharedFactionIconsSystem
+
+                int currentTotal = squadData.SergeantCount + squadData.MemberCount;
+                if (currentTotal >= squadConfig.MaxSize)
+                    continue; // Squad is full
+
+                if (findForSergeant)
+                {
+                    if (squadData.SergeantCount == 0) // Slot for sergeant is open
+                    {
+                        if (currentTotal < minMembers) // Prefer less populated squads for sergeants too
+                        {
+                            minMembers = currentTotal;
+                            bestSquad = squadNameKey;
+                        }
+                    }
+                }
+                else // Finding for member
+                {
+                    if (currentTotal < minMembers)
+                    {
+                        minMembers = currentTotal;
+                        bestSquad = squadNameKey;
+                    }
+                }
+            }
+            return bestSquad;
+        }
+
+        public void RecalculateAllCivFactionSquadCounts(CivTDMFactionsComponent civTDMComp)
+        {
+            // Reset counts
+            foreach (var squadDataDict in new[] { civTDMComp.Faction1Squads, civTDMComp.Faction2Squads })
+            {
+                foreach (var squadData in squadDataDict.Values)
+                {
+                    squadData.MemberCount = 0;
+                    squadData.SergeantCount = 0;
+                }
+            }
+
+            var memberIconIds = Squads.ToDictionary(kvp => kvp.Value.MemberIconId, kvp => kvp.Key);
+            var sergeantIconIds = Squads.ToDictionary(kvp => kvp.Value.SergeantIconId, kvp => kvp.Key);
+
+            var query = _entityManager.EntityQueryEnumerator<ShowFactionIconsComponent>();
+            while (query.MoveNext(out _, out var sfiComp))
+            {
+                if (sfiComp.SquadIcon == null || sfiComp.BelongsToCivFactionId == null || sfiComp.AssignSquad == false)
+                    continue;
+
+                var targetSquadsDict = GetCivFactionSquads(civTDMComp, sfiComp.BelongsToCivFactionId);
+                if (targetSquadsDict == null)
+                    continue;
+
+                if (memberIconIds.TryGetValue(sfiComp.SquadIcon, out var squadNameKeySgt) && targetSquadsDict.TryGetValue(squadNameKeySgt, out var squadData))
+                {
+                    if (sfiComp.JobIcon == "JobIconISgt")
+                    {
+                        squadData.SergeantCount++;
+                    }
+                    else
+                    {
+                        squadData.MemberCount++;
+                    }
+                }
+
+            }
+            // Mark CivTDMFactionsComponent as dirty. Need to get the MapUid from the TransformComponent.
+            if (_entityManager.TryGetComponent<TransformComponent>(civTDMComp.Owner, out var xform) && xform.MapUid.HasValue)
+            {
+                Dirty(xform.MapUid.Value, civTDMComp);
+            }
+        }
+    }
+}
+

+ 71 - 6
Content.Server/StationRecords/Systems/StationRecordsSystem.cs

@@ -3,6 +3,7 @@
 using Content.Server.Forensics;
 using Content.Shared.Access.Components;
 using Content.Shared.Forensics.Components;
+using Content.Server.Overlays; // Namespace for FactionIconsSystem
 using Content.Shared.GameTicking;
 using Content.Shared.Inventory;
 using Content.Shared.PDA;
@@ -12,6 +13,9 @@
 using Robust.Shared.Enums;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
+using Content.Shared.Overlays; // Namespace for ShowFactionIconsComponent
+using Content.Shared.Civ14.CivTDMFactions;
+using Content.Shared.NPC.Components; // Namespace for CivTDMFactionsComponent
 
 namespace Content.Server.StationRecords.Systems;
 
@@ -41,6 +45,7 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly IdCardSystem _idCard = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly FactionIconsSystem _factionIcons = default!; // Added dependency
 
     public override void Initialize()
     {
@@ -55,7 +60,67 @@ private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
         if (!TryComp<StationRecordsComponent>(args.Station, out var stationRecords))
             return;
 
+        // Create the general record first
         CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId, stationRecords);
+
+        // --- Attempt Squad Assignment ---
+        if (TryComp<ShowFactionIconsComponent>(args.Mob, out var sfiComponent))
+        {
+            // Determine if player wants to be a sergeant (e.g., based on job)
+            bool wantsToBeSergeant = sfiComponent.JobIcon == "JobIconISgt"; // Example: "JobIconISgt" implies sergeant role
+
+            // Determine player's CivFaction (this is crucial and needs proper game logic)
+            // For this example, let's assume a simple alternating assignment or based on JobId.
+            // In a real game, this would come from team selection, game mode logic, etc.
+            string? playerCivFactionId = null;
+
+            // Query for the CivTDMFactionsComponent (assuming one exists on a game rule or map entity)
+            var civQuery = EntityQueryEnumerator<CivTDMFactionsComponent>();
+            CivTDMFactionsComponent? civTDMComp = null;
+            if (civQuery.MoveNext(out _, out civTDMComp))
+            {
+                // Example: Assign to Faction1Id if JobId contains "Faction1", else Faction2Id
+                // This is placeholder logic. Replace with your actual faction assignment logic.
+                if (args.JobId != null) // Ensure JobId is not null
+                {
+                    if (TryComp<NpcFactionMemberComponent>(args.Mob, out var factionComp))
+                    {
+                        foreach (var faction in factionComp.Factions)
+                        {
+                            if (civTDMComp.Faction1Id == null || faction == civTDMComp.Faction1Id)
+                            {
+                                playerCivFactionId = civTDMComp.Faction1Id; // This is already a string, no need for ProtoId conversion here
+                                break;
+                            }
+                            else
+                            {
+                                playerCivFactionId = civTDMComp.Faction2Id;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                // Fallback or default assignment if not determined by job
+                if (playerCivFactionId == null)
+                {
+                    // Simplistic: assign to Faction1Id by default if not specified or if factionComp.Factions was empty/null.
+                    // Or implement round-robin, or based on current team populations.
+                    playerCivFactionId = civTDMComp.Faction1Id;
+                }
+            }
+
+            if (playerCivFactionId != null)
+            {
+                sfiComponent.BelongsToCivFactionId = playerCivFactionId; // Store it on the component
+                _factionIcons.AttemptAssignPlayerToSquad(args.Mob, playerCivFactionId, wantsToBeSergeant, sfiComponent);
+            }
+            else
+            {
+                Log.Warning($"Could not determine CivFaction for player {ToPrettyString(args.Mob)} with job {args.JobId}. Squad assignment skipped.");
+            }
+        }
+        // --- End Squad Assignment ---
     }
 
     private void OnRename(ref EntityRenamedEvent ev)
@@ -65,13 +130,13 @@ private void OnRename(ref EntityRenamedEvent ev)
         // given entity is a card and the card itself is the key the record will be mistakenly renamed to the card's name
         // if we don't return early.
         // We also do not include the PDA itself being renamed, as that triggers the same event (e.g. for chameleon PDAs).
-        if (HasComp<IdCardComponent>(ev.Uid) ||  HasComp<PdaComponent>(ev.Uid))
+        if (HasComp<IdCardComponent>(ev.Uid) || HasComp<PdaComponent>(ev.Uid))
             return;
 
         if (_idCard.TryFindIdCard(ev.Uid, out var idCard))
         {
             if (TryComp(idCard, out StationRecordKeyStorageComponent? keyStorage)
-                && keyStorage.Key is {} key)
+                && keyStorage.Key is { } key)
             {
                 if (TryGetRecord<GeneralStationRecord>(key, out var generalRecord))
                 {
@@ -146,7 +211,7 @@ private void OnRename(ref EntityRenamedEvent ev)
 
         // when adding a record that already exists use the old one
         // this happens when respawning as the same character
-        if (GetRecordByName(station, name, records) is {} id)
+        if (GetRecordByName(station, name, records) is { } id)
         {
             SetIdKey(idUid, new StationRecordKey(id, station));
             return;
@@ -183,11 +248,11 @@ private void OnRename(ref EntityRenamedEvent ev)
     /// </summary>
     public void SetIdKey(EntityUid? uid, StationRecordKey key)
     {
-        if (uid is not {} idUid)
+        if (uid is not { } idUid)
             return;
 
         var keyStorageEntity = idUid;
-        if (TryComp<PdaComponent>(idUid, out var pda) && pda.ContainedId is {} id)
+        if (TryComp<PdaComponent>(idUid, out var pda) && pda.ContainedId is { } id)
         {
             keyStorageEntity = id;
         }
@@ -283,7 +348,7 @@ public bool TryGetRandomRecord<T>(Entity<StationRecordsComponent?> ent, [NotNull
     public string RecordName(StationRecordKey key)
     {
         if (!TryGetRecord<GeneralStationRecord>(key, out var record))
-           return string.Empty;
+            return string.Empty;
 
         return record.Name;
     }

+ 2 - 2
Content.Shared/Civ14/CivResearch/CivResearchComponent.cs

@@ -26,11 +26,11 @@ public sealed partial class CivResearchComponent : Component
     public float ResearchLevel { get; set; } = 0f;
     /// <summary>
     /// For autoresearch, how much research increases per tick.
-    /// This defaults to 100 levels per day.
+    /// This defaults to 100 levels per hour.
     /// </summary>
 
     [DataField("researchSpeed"), AutoNetworkedField]
-    public float ResearchSpeed { get; set; } = 0.000057f;
+    public float ResearchSpeed { get; set; } = 0.001368f;
     /// <summary>
     /// The maximum research level.
     /// Should probably stay below 900 as 9 is used as the research level for disabled and futuristic stuff.

+ 66 - 0
Content.Shared/Civ14/CivTDMFactions/CivTDMFactionsComponent.cs

@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Serialization; // Added this line
+using System; // Added for [Serializable]
+
+namespace Content.Shared.Civ14.CivTDMFactions;
+
+[AutoGenerateComponentState]
+[RegisterComponent]
+[NetworkedComponent]
+public sealed partial class CivTDMFactionsComponent : Component
+{
+    //TODO: Consider making FactionId a prototype for more structured data (color, sprite, etc.)
+    /// <summary>
+    /// The name of faction1
+    /// </summary>
+    [DataField("faction1Id", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), AutoNetworkedField]
+    public string? Faction1Id { get; set; }
+
+    /// <summary>
+    /// The name of faction2
+    /// </summary>
+    [DataField("faction2Id", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), AutoNetworkedField]
+    public string? Faction2Id { get; set; }
+
+    /// <summary>
+    /// Squads belonging to faction 1. Key is squad name (e.g., "Alpha").
+    /// </summary>
+    [DataField("faction1Squads"), AutoNetworkedField]
+    public Dictionary<string, SquadData> Faction1Squads { get; set; } = new()
+    {
+        { "Alpha", new SquadData() },
+        { "Bravo", new SquadData() },
+        { "Charlie", new SquadData() }
+    };
+
+    /// <summary>
+    /// Squads belonging to faction 2. Key is squad name (e.g., "Alpha").
+    /// </summary>
+    [DataField("faction2Squads"), AutoNetworkedField]
+    public Dictionary<string, SquadData> Faction2Squads { get; set; } = new()
+    {
+        { "Alpha", new SquadData() },
+        { "Bravo", new SquadData() },
+        { "Charlie", new SquadData() }
+    };
+}
+
+/// <summary>
+/// Holds data for a single squad, like member counts.
+/// </summary>
+[DataDefinition, NetSerializable, Serializable]
+public sealed partial class SquadData
+{
+    [DataField("sergeantCount")] // Removed AutoNetworkedField
+    public int SergeantCount { get; set; } = 0;
+
+    [DataField("memberCount")] // Removed AutoNetworkedField
+    public int MemberCount { get; set; } = 0;
+
+    // You could add MaxSize here if it's per-squad rather than global from ShowFactionIconsSystem
+    // [DataField("maxSize")]
+    // public int MaxSize { get; set; } = 3;
+}

+ 3 - 5
Content.Shared/Inventory/InventorySystem.Relay.cs

@@ -25,6 +25,9 @@ namespace Content.Shared.Inventory;
 
 public partial class InventorySystem
 {
+    /// <summary>
+    /// Subscribes the inventory component to a range of events, enabling automatic relaying of relevant events to equipped items in specified inventory slots.
+    /// </summary>
     public void InitializeRelay()
     {
         SubscribeLocalEvent<InventoryComponent, DamageModifyEvent>(RelayInventoryEvent);
@@ -67,11 +70,6 @@ public void InitializeRelay()
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowCriminalRecordIconsComponent>>(RefRelayInventoryEvent);
 
         SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowFactionIconsComponent>>(RefRelayInventoryEvent);
-        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowFrenchFactionIconsComponent>>(RefRelayInventoryEvent);
-        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowEnglishFactionIconsComponent>>(RefRelayInventoryEvent);
-        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowGermanFactionIconsComponent>>(RefRelayInventoryEvent);
-        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowSovietFactionIconsComponent>>(RefRelayInventoryEvent);
-        SubscribeLocalEvent<InventoryComponent, RefreshEquipmentHudEvent<ShowUsFactionIconsComponent>>(RefRelayInventoryEvent);
 
         SubscribeLocalEvent<InventoryComponent, GetVerbsEvent<EquipmentVerb>>(OnGetEquipmentVerbs);
     }

+ 1 - 1
Content.Shared/NPC/Systems/NpcFactionSystem.cs

@@ -309,7 +309,7 @@ private void RefreshFactions()
     {
         _factions = _proto.EnumeratePrototypes<NpcFactionPrototype>().ToFrozenDictionary(
             faction => faction.ID,
-            faction =>  new FactionData
+            faction => new FactionData
             {
                 Friendly = faction.Friendly.ToHashSet(),
                 Hostile = faction.Hostile.ToHashSet()

+ 21 - 41
Content.Shared/Overlays/ShowFactionIconsComponent.cs

@@ -7,63 +7,43 @@ namespace Content.Shared.Overlays;
 /// <summary>
 ///     This component allows you to see faction icons above mobs.
 /// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class ShowFactionIconsComponent : Component
 {
 
     /// <summary>
     /// The faction icon to display
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "HostileFaction";
-}
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ShowEnglishFactionIconsComponent : Component
-{
-
+    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>)), AutoNetworkedField]
+    public string FactionIcon { get; set; } = "HostileFaction";
     /// <summary>
-    /// The faction icon to display
+    /// The job icon to display (if any)
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "EnglishFaction";
-}
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ShowFrenchFactionIconsComponent : Component
-{
-
+    [DataField("jobIcon", customTypeSerializer: typeof(PrototypeIdSerializer<JobIconPrototype>)), AutoNetworkedField]
+    public string JobIcon { get; set; } = "JobIconSoldier";
     /// <summary>
-    /// The faction icon to display
+    /// If this role is part of one of the squads
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "FrenchFaction";
-}
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ShowGermanFactionIconsComponent : Component
-{
-
+    [DataField("assignSquad"), AutoNetworkedField]
+    public bool AssignSquad { get; set; } = false;
     /// <summary>
-    /// The faction icon to display
+    /// The specific squad icon (e.g., "JobIconSquadAlphaSergeant") assigned by the server.
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "GermanFaction";
-}
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ShowSovietFactionIconsComponent : Component
-{
+    [DataField("squadIcon", customTypeSerializer: typeof(PrototypeIdSerializer<JobIconPrototype>)), AutoNetworkedField]
+    public string? SquadIcon { get; set; }
 
     /// <summary>
-    /// The faction icon to display
+    /// The key/name of the squad the entity is assigned to (e.g., "Alpha").
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "SovietFaction";
-}
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ShowUsFactionIconsComponent : Component
-{
+    [DataField("assignedSquadNameKey"), AutoNetworkedField]
+    public string? AssignedSquadNameKey { get; set; }
+
+    [DataField("isSergeantInSquad"), AutoNetworkedField]
+    public bool IsSergeantInSquad { get; set; }
 
     /// <summary>
-    /// The faction icon to display
+    /// Identifier for the major CivTeamDeathmatch Faction this entity belongs to (e.g., Faction1Id or Faction2Id from CivTDMFactionsComponent).
     /// </summary>
-    [DataField("factionIcon", customTypeSerializer: typeof(PrototypeIdSerializer<FactionIconPrototype>))]
-    public string FactionIcon = "UsFaction";
+    [DataField("belongsToCivFactionId"), AutoNetworkedField]
+    public string? BelongsToCivFactionId { get; set; }
 }

+ 131 - 0
Content.Shared/Overlays/ShowFactionIconsSystem.cs

@@ -0,0 +1,131 @@
+using Content.Shared.StatusIcon.Components; // For JobIconPrototype if you use it directly
+using Robust.Shared.Prototypes;
+using System.Linq; // For LINQ queries if needed for checking existing squad members
+
+namespace Content.Shared.Overlays;
+
+public abstract class SharedFactionIconsSystem : EntitySystem
+{
+    [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+
+    // Example: Define your squad configurations. This could be more dynamic.
+    // You'd likely have prototypes for squads themselves eventually.
+    protected static readonly Dictionary<string, SquadConfig> Squads = new()
+    {
+        { "Alpha", new SquadConfig("JobIconSquad1", "JobIconSquad1", 20) },
+        { "Bravo", new SquadConfig("JobIconSquad2", "JobIconSquad2", 20) },
+        { "Charlie", new SquadConfig("JobIconSquad3", "JobIconSquad3", 20) },
+        // Add more squads here: "SquadName", "MemberIconId", "SergeantIconId", MaxSize
+    };
+    protected record SquadConfig(string MemberIconId, string SergeantIconId, int MaxSize);
+
+    /// <summary>
+    /// Call this on the SERVER to attempt to assign an entity to a squad.
+    /// </summary>
+    public virtual bool TryAssignToSquad(EntityUid uid, string squadName, bool assignAsSergeant, ShowFactionIconsComponent? component = null)
+    {
+        if (!Resolve(uid, ref component, logMissing: false))
+            return false; // Only run on server and if component exists
+
+        if (!Squads.TryGetValue(squadName, out var config))
+            return false; // Squad doesn't exist
+
+        // --- Server-Side Logic to check rules based on new explicit fields ---
+        int currentOccupantsInSquad = 0;
+        bool sergeantRoleFilledInSquad = false;
+        EntityUid? existingSergeantUid = null;
+        ShowFactionIconsComponent? oldSergeantComp = null; // For demotion
+
+        var query = AllEntityQuery<ShowFactionIconsComponent>();
+        while (query.MoveNext(out var otherUid, out var otherComp))
+        {
+            if (otherComp.AssignSquad && otherComp.AssignedSquadNameKey == squadName)
+            {
+                currentOccupantsInSquad++;
+                if (otherComp.IsSergeantInSquad)
+                {
+                    sergeantRoleFilledInSquad = true;
+                    existingSergeantUid = otherUid;
+                }
+            }
+        }
+
+        // Current state of the entity 'uid' being assigned
+        bool entityIsAlreadyInThisSquad = component.AssignSquad && component.AssignedSquadNameKey == squadName;
+        bool entityIsAlreadySergeantOfThisSquad = entityIsAlreadyInThisSquad && component.IsSergeantInSquad;
+
+        if (assignAsSergeant)
+        {
+            // Trying to make 'uid' the sergeant.
+            // If another sergeant exists and it's not 'uid', 'uid' will replace them (demotion handled later).
+            // Check if adding 'uid' (if not already in squad) would exceed MaxSize.
+            if (!entityIsAlreadyInThisSquad && currentOccupantsInSquad >= config.MaxSize)
+            {
+                Log.Debug($"Cannot assign {ToPrettyString(uid)} as Sergeant to squad {squadName}. Squad is full ({currentOccupantsInSquad}/{config.MaxSize}) and entity is not already in squad.");
+                return false; // Squad is full, cannot add a new person as sergeant.
+            }
+        }
+        else // Assigning as member
+        {
+            // Trying to make 'uid' a member.
+            if (entityIsAlreadySergeantOfThisSquad)
+            {
+                // 'uid' is demoting itself. Count doesn't change. Fine.
+            }
+            else if (entityIsAlreadyInThisSquad && !component.IsSergeantInSquad) // Already a member
+            {
+                // 'uid' is re-affirming member status. Count doesn't change. Fine.
+            }
+            else // 'uid' is not in this squad (or in another squad, or not assigned).
+            {
+                if (currentOccupantsInSquad >= config.MaxSize)
+                {
+                    Log.Debug($"Cannot assign {ToPrettyString(uid)} as Member to squad {squadName}. Squad is full ({currentOccupantsInSquad}/{config.MaxSize}).");
+                    return false; // Squad is full, cannot add a new member.
+                }
+            }
+        }
+
+        // --- Update component ---
+        component.AssignSquad = true;
+        component.AssignedSquadNameKey = squadName;
+        component.IsSergeantInSquad = assignAsSergeant;
+        component.SquadIcon = assignAsSergeant ? config.SergeantIconId : config.MemberIconId;
+        Dirty(uid, component); // Mark component as dirty to ensure state is synced
+
+        // If 'uid' became sergeant, and there was a *different* existing sergeant in this squad, demote the old one.
+        if (assignAsSergeant && sergeantRoleFilledInSquad && existingSergeantUid.HasValue && existingSergeantUid.Value != uid)
+        {
+            if (Resolve(existingSergeantUid.Value, ref oldSergeantComp, logMissing: false))
+            {
+                // Ensure this old sergeant was indeed for *this* squad before demoting.
+                if (oldSergeantComp.AssignSquad && oldSergeantComp.AssignedSquadNameKey == squadName && oldSergeantComp.IsSergeantInSquad)
+                {
+                    oldSergeantComp.IsSergeantInSquad = false;
+                    oldSergeantComp.SquadIcon = config.MemberIconId; // Demote to member icon
+                    // oldSergeantComp.AssignedSquadNameKey remains squadName
+                    Dirty(existingSergeantUid.Value, oldSergeantComp);
+                    Log.Info($"Demoted {ToPrettyString(existingSergeantUid.Value)} from sergeant in squad {squadName} as {ToPrettyString(uid)} took the role.");
+                }
+            }
+        }
+
+
+        Log.Info($"Assigned {ToPrettyString(uid)} to squad {squadName} as {(assignAsSergeant ? "Sergeant" : "Member")}. Icon: {component.SquadIcon}");
+        return true;
+    }
+
+    public virtual void RemoveFromSquad(EntityUid uid, ShowFactionIconsComponent? component = null)
+    {
+        if (!Resolve(uid, ref component, logMissing: false))
+            return;
+
+        component.AssignSquad = false;
+        component.SquadIcon = null;
+        component.AssignedSquadNameKey = null;
+        component.IsSergeantInSquad = false;
+        Dirty(uid, component);
+        Log.Info($"Removed {ToPrettyString(uid)} from squad.");
+    }
+}

+ 1 - 1
Content.Shared/_RMC14/Mortar/MortarComponent.cs

@@ -13,7 +13,7 @@ public sealed partial class MortarComponent : Component
     public string ContainerId = "rmc_mortar_container";
 
     [DataField, AutoNetworkedField]
-    public TimeSpan DeployDelay = TimeSpan.FromSeconds(4);
+    public TimeSpan DeployDelay = TimeSpan.FromSeconds(8);
 
     [DataField, AutoNetworkedField]
     public TimeSpan TargetDelay = TimeSpan.FromSeconds(3);

+ 1 - 1
LICENSE.TXT

@@ -6,7 +6,7 @@ Most assets are licensed under CC-BY-SA 3.0, https://creativecommons.org/license
 unless stated otherwise. Assets have their license and the copyright in the metadata file.
 
 Note that some assets are licensed under the non-commercial CC-BY-NC-SA 3.0,
-https://creativecommons.org/licenses/by-nc-sa/3.0/,or similar non-commercial
+https://creativecommons.org/licenses/by-nc-sa/3.0/, or similar non-commercial
 licenses and will need to be removed if you wish to use this project commercially.
 
 Assets ported over from Civ13 are licenced under GNU Affero General Public License v3.0 (AGPLv3),

+ 3 - 0
Resources/Maps/civ/tdm/camp_ww2.yml

@@ -43,6 +43,9 @@ entities:
       maxResearch: 600
       researchLevel: 600
       researchEnabled: False
+    - type: CivTDMFactions
+      faction1Id: German
+      faction2Id: Soviet
   - uid: 2
     components:
     - type: MetaData

+ 3 - 0
Resources/Maps/civ/tdm/hotel.yml

@@ -55,6 +55,9 @@ entities:
       researchLevel: 600
       researchEnabled: False
     - type: FTLDestination
+    - type: CivTDMFactions
+      faction1Id: US
+      faction2Id: SovietCW
   - uid: 2
     components:
     - type: MetaData

+ 3 - 0
Resources/Maps/civ/tdm/opushka.yml

@@ -43,6 +43,9 @@ entities:
       researchLevel: 600
       researchEnabled: False
     - type: FTLDestination
+    - type: CivTDMFactions
+      faction1Id: German
+      faction2Id: Soviet
   - uid: 2
     components:
     - type: MetaData

文件差異過大導致無法顯示
+ 62 - 526
Resources/Prototypes/Civ14/Entities/Clothing/entities_clothing_accessories.yml


+ 48 - 184
Resources/Prototypes/Civ14/Entities/Clothing/entities_clothing_shoes.yml

@@ -8,15 +8,7 @@
       sprite: Civ14/Clothing/exported/shoes/white.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/white.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_shoes
@@ -85,15 +77,7 @@
       sprite: Civ14/Clothing/exported/shoes/red.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/red.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_red_shoes
@@ -111,15 +95,7 @@
       sprite: Civ14/Clothing/exported/shoes/orange.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/orange.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_orange_shoes
@@ -364,15 +340,7 @@
       sprite: Civ14/Clothing/exported/shoes/japboots_ww2_navy.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/japboots_ww2_navy.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_Black_leather_boots
@@ -465,17 +433,7 @@
       sprite: Civ14/Clothing/exported/shoes/sandals.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/sandals.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
     - type: Construction
-
       graph: civ13_shoes_sandals
       node: end
       cost: 1
@@ -491,15 +449,7 @@
       sprite: Civ14/Clothing/exported/shoes/leather.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/leather.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_leather_shoes
@@ -517,15 +467,7 @@
       sprite: Civ14/Clothing/exported/shoes/black.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/black.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_magic_shoes
@@ -544,15 +486,7 @@
       sprite: Civ14/Clothing/exported/shoes/laceups.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/laceups.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_laceup_shoes
@@ -573,8 +507,8 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.8
-          Slash: 0.8
+          Blunt: 0.9
+          Slash: 0.9
           Arrow: 0.9
           Heat: 0.75
           Radiation: 0.65
@@ -595,15 +529,7 @@
       sprite: Civ14/Clothing/exported/shoes/gator_laceups.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/gator_laceups.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_alligator_scale_laceup_shoes
@@ -621,15 +547,7 @@
       sprite: Civ14/Clothing/exported/shoes/chad.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/chad.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_chad_shoes
@@ -647,15 +565,7 @@
       sprite: Civ14/Clothing/exported/shoes/flipflops.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/flipflops.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_flip_flops
@@ -748,15 +658,7 @@
       sprite: Civ14/Clothing/exported/shoes/roman.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/roman.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 0.4
-          Slash: 0.4
-          Arrow: 0.95
-          Heat: 0.75
     - type: Construction
-
       graph: civ13_shoes_sandals_1
       node: end
       cost: 2
@@ -772,14 +674,7 @@
       sprite: Civ14/Clothing/exported/shoes/aztec_sandals.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/aztec_sandals.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 0.9
-          Slash: 0.9
-          Arrow: 0.95
     - type: Construction
-
       graph: civ13_shoes_aztec_sandals
       node: end
       cost: 2
@@ -798,8 +693,8 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.8
-          Slash: 0.8
+          Blunt: 0.9
+          Slash: 0.9
           Arrow: 0.9
           Heat: 0.75
           Radiation: 0.65
@@ -823,8 +718,8 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.8
-          Slash: 0.8
+          Blunt: 0.9
+          Slash: 0.9
           Arrow: 0.9
           Heat: 0.75
           Radiation: 0.65
@@ -848,10 +743,10 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.25
-          Slash: 0.25
+          Blunt: 0.8
+          Slash: 0.8
           Piercing: 0.9
-          Arrow: 0.4
+          Arrow: 0.8
           Heat: 0.92
     - type: Construction
 
@@ -1047,10 +942,10 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.7
-          Slash: 0.7
+          Blunt: 0.95
+          Slash: 0.95
           Piercing: 0.9
-          Arrow: 0.75
+          Arrow: 0.95
           Heat: 0.85
           Radiation: 0.95
     - type: Construction
@@ -1073,9 +968,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.7
-          Slash: 0.7
-          Arrow: 0.8
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
     - type: Construction
 
@@ -1097,9 +992,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.19
-          Slash: 0.19
-          Arrow: 0.8
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
           Radiation: 0.7
     - type: Construction
@@ -1122,8 +1017,8 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.7
-          Slash: 0.7
+          Blunt: 0.9
+          Slash: 0.9
           Piercing: 0.9
           Arrow: 0.8
           Heat: 0.75
@@ -1147,9 +1042,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.3
-          Slash: 0.3
-          Arrow: 0.6
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
           Radiation: 0.75
     - type: Construction
@@ -1172,9 +1067,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.3
-          Slash: 0.3
-          Arrow: 0.6
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
           Radiation: 0.75
     - type: Construction
@@ -1197,9 +1092,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.3
-          Slash: 0.3
-          Arrow: 0.6
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
           Radiation: 0.75
     - type: Construction
@@ -1222,9 +1117,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.3
-          Slash: 0.3
-          Arrow: 0.6
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.75
           Radiation: 0.75
     - type: Construction
@@ -1247,9 +1142,9 @@
     - type: Armor
       modifiers:
         coefficients:
-          Blunt: 0.5
-          Slash: 0.5
-          Arrow: 0.7
+          Blunt: 0.95
+          Slash: 0.95
+          Arrow: 0.95
           Heat: 0.8
           Radiation: 0.8
     - type: Construction
@@ -1391,13 +1286,6 @@
       sprite: Civ14/Clothing/exported/shoes/geta.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/geta.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 0.95
-          Slash: 0.95
-          Arrow: 0.97
-          Heat: 0.95
     - type: Construction
 
       graph: civ13_shoes_geta_sandals
@@ -1515,15 +1403,7 @@
       sprite: Civ14/Clothing/exported/shoes/lizard_ankleboots.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/lizard_ankleboots.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_lizard_scale_ankle_boots
@@ -1541,15 +1421,7 @@
       sprite: Civ14/Clothing/exported/shoes/lizard_laceups.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/lizard_laceups.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_lizard_scale_laceup_shoes
@@ -1618,15 +1490,7 @@
       sprite: Civ14/Clothing/exported/shoes/football.rsi
     - type: Clothing
       sprite: Civ14/Clothing/exported/shoes/football.rsi
-    - type: Armor
-      modifiers:
-        coefficients:
-          Blunt: 1
-          Slash: 1
-          Piercing: 1
-          Arrow: 1
-          Heat: 1
-          Radiation: 1
+
     - type: Construction
 
       graph: civ13_shoes_football_trainers

+ 18 - 18
Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortar_shells.yml

@@ -4,12 +4,12 @@
   id: MortarAmmoBase
   name: base mortar shell
   components:
-  - type: Sprite
-    sprite: Civ14/Objects/Mortars/shells.rsi
-    state: mortar_ammo_he
-  - type: Item
-    size: Huge
-  - type: MortarShell
+    - type: Sprite
+      sprite: Civ14/Objects/Mortars/shells.rsi
+      state: mortar_ammo_he
+    - type: Item
+      size: Huge
+    - type: MortarShell
 
 - type: entity
   parent: MortarAmmoBase
@@ -17,15 +17,15 @@
   name: high explosive mortar shell
   description: A mortar shell packed with high explosive. Drop into a mortar tube to activate.
   components:
-  - type: Item
-  - type: Sprite
-    sprite: Civ14/Objects/Mortars/shells.rsi
-    state: mortar_ammo_he
-  - type: MortarShell
-  - type: Explosive
-    explosionType: Default
-    maxIntensity: 10 # these values need tuning to im sure
-    intensitySlope: 3
-    totalIntensity: 120
-    canCreateVacuum: false
-    deleteAfterExplosion: true
+    - type: Item
+    - type: Sprite
+      sprite: Civ14/Objects/Mortars/shells.rsi
+      state: mortar_ammo_he
+    - type: MortarShell
+    - type: Explosive
+      explosionType: Default
+      maxIntensity: 10 # these values need tuning to im sure
+      intensitySlope: 3
+      totalIntensity: 120
+      canCreateVacuum: false
+      deleteAfterExplosion: true

+ 53 - 55
Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortars.yml

@@ -4,60 +4,58 @@
   name: portable mortar
   description: A support weapon capable of droping bombs on heads from a distance. This one seems rather modern.
   components:
-  - type: Sprite
-    sprite: Civ14/Objects/Mortars/mortar.rsi
-    layers:
-    - map: [ "mortar" ]
-      state: mortar_carry
-  - type: Fixtures
-    fixtures:
-      fix1:
-        shape:
-          !type:PhysShapeAabb
-          bounds: "-0.25,-0.25,0.25,0.25"
-        density: 20
-        mask:
-        - ItemMask
-        restitution: 0.3
-        friction: 0.2
-      mortar:
-        shape:
-          !type:PhysShapeAabb
-          bounds: "-0.49,-0.49,0.49,0.49"
-        density: 20
-        layer:
-        - Impassable
-        mask:
-        - Impassable
-        hard: false
-  - type: CollisionWake
-    enabled: false
-  - type: Anchorable
-    flags:
-    - Unanchorable
-  - type: Item
-    size: Huge
-  - type: Mortar
-  - type: Damageable
-    damageContainer: Inorganic
-  - type: UserInterface
-    interfaces:
-      enum.MortarUiKey.Key:
-        type: MortarBui
-  - type: ActivatableUI
-    key: enum.MortarUiKey.Key
-  - type: Appearance
-  - type: GenericVisualizer
-    visuals:
-      enum.MortarVisualLayers.State:
+    - type: Sprite
+      sprite: Civ14/Objects/Mortars/mortar.rsi
+      layers:
+        - map: ["mortar"]
+          state: mortar_carry
+    - type: Fixtures
+      fixtures:
+        fix1:
+          shape: !type:PhysShapeAabb
+            bounds: "-0.25,-0.25,0.25,0.25"
+          density: 20
+          mask:
+            - ItemMask
+          restitution: 0.3
+          friction: 0.2
         mortar:
-          Item: { state: mortar_carry }
-          Deployed: { state: mortar_deploy }
-  - type: MeleeSound
-    soundGroups:
-      Brute:
-        path:
-          "/Audio/Effects/metal_break1.ogg"
+          shape: !type:PhysShapeAabb
+            bounds: "-0.49,-0.49,0.49,0.49"
+          density: 20
+          layer:
+            - Impassable
+          mask:
+            - Impassable
+          hard: false
+    - type: CollisionWake
+      enabled: false
+    - type: Anchorable
+      flags:
+        - Unanchorable
+    - type: Item
+      size: Huge
+    - type: Mortar
+      fireDelay: 10
+    - type: Damageable
+      damageContainer: Inorganic
+    - type: UserInterface
+      interfaces:
+        enum.MortarUiKey.Key:
+          type: MortarBui
+    - type: ActivatableUI
+      key: enum.MortarUiKey.Key
+    - type: Appearance
+    - type: GenericVisualizer
+      visuals:
+        enum.MortarVisualLayers.State:
+          mortar:
+            Item: { state: mortar_carry }
+            Deployed: { state: mortar_deploy }
+    - type: MeleeSound
+      soundGroups:
+        Brute:
+          path: "/Audio/Effects/metal_break1.ogg"
 
 - type: entity
   parent: MortarKit
@@ -65,5 +63,5 @@
   name: old portable mortar
   description: A support weapon capable of droping bombs on heads from a distance.
   components:
-  - type: Sprite
-    sprite: Civ14/Objects/Mortars/mortar_old.rsi
+    - type: Sprite
+      sprite: Civ14/Objects/Mortars/mortar_old.rsi

+ 6 - 0
Resources/Prototypes/Civ14/Entities/Structures/Walls/walls.yml

@@ -781,6 +781,9 @@
   description: A modern, sturdy concrete wall.
   suffix: ""
   components:
+    - type: Damageable
+      damageContainer: StructuralInorganic
+      damageModifierSet: Inert
     - type: Destructible
       thresholds:
         - trigger: !type:DamageTrigger
@@ -814,6 +817,9 @@
   description: A classic red brick wall.
   suffix: ""
   components:
+    - type: Damageable
+      damageContainer: StructuralInorganic
+      damageModifierSet: Inert
     - type: Destructible
       thresholds:
         - trigger: !type:DamageTrigger

+ 0 - 15
Resources/Prototypes/Civ14/StatusIcon/faction.yml

@@ -187,11 +187,6 @@
     components:
       - ShowAntagIcons
       - ShowFactionIcons
-      - ShowEnglishFactionIcons
-      - ShowFrenchFactionIcons
-      - ShowGermanFactionIcons
-      - ShowSovietFactionIcons
-      - ShowUsFactionIcons
   icon:
     sprite: /Textures/Interface/Misc/civ_hud_countries.rsi
     state: ger_basic
@@ -522,11 +517,6 @@
     components:
       - ShowAntagIcons
       - ShowFactionIcons
-      - ShowEnglishFactionIcons
-      - ShowFrenchFactionIcons
-      - ShowGermanFactionIcons
-      - ShowSovietFactionIcons
-      - ShowUsFactionIcons
   icon:
     sprite: /Textures/Interface/Misc/civ_hud_countries.rsi
     state: sov_basic
@@ -648,11 +638,6 @@
     components:
       - ShowAntagIcons
       - ShowFactionIcons
-      - ShowEnglishFactionIcons
-      - ShowFrenchFactionIcons
-      - ShowGermanFactionIcons
-      - ShowSovietFactionIcons
-      - ShowUsFactionIcons
   icon:
     sprite: /Textures/Interface/Misc/civ_hud_countries.rsi
     state: us_basic

+ 74 - 18
Resources/Prototypes/Civ14/StatusIcon/job.yml

@@ -1,5 +1,5 @@
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 1
   id: JobIconCommander
@@ -9,7 +9,7 @@
   jobName: job-name-commander
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   id: JobIconIMaj
   priority: 2
@@ -19,7 +19,7 @@
   jobName: job-name-i-maj
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 3
   id: JobIconICpt
@@ -29,7 +29,7 @@
   jobName: job-name-i-cpt
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 4
   id: JobIconOfficer
@@ -39,7 +39,7 @@
   jobName: job-name-officer
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 4
   id: JobIconILt
@@ -49,7 +49,7 @@
   jobName: job-name-i-lt
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 5
   id: JobIconISsgt
@@ -59,7 +59,7 @@
   jobName: job-name-i-ssgt
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 6
   id: JobIconISgt
@@ -68,7 +68,7 @@
     state: i_sgt
   jobName: job-name-i-sgt
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 6
   id: JobIconNco
@@ -77,7 +77,7 @@
     state: nco
   jobName: job-name-nco
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 7
   id: JobIconICpl
@@ -86,7 +86,7 @@
     state: i_cpl
   jobName: job-name-i-cpl
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 8
   id: JobIconDesignatedMarksman
@@ -96,7 +96,7 @@
   jobName: job-name-designated-marksman
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 8
   id: JobIconMedic
@@ -106,7 +106,7 @@
   jobName: job-name-medic
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 8
   id: JobIconMg
@@ -116,7 +116,7 @@
   jobName: job-name-mg
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconRifleman
@@ -126,7 +126,7 @@
   jobName: job-name-rifleman
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconSoldier
@@ -136,7 +136,7 @@
   jobName: job-name-soldier
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconSword
@@ -146,7 +146,7 @@
   jobName: job-name-infantry-sword
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconHeavy
@@ -156,7 +156,7 @@
   jobName: job-name-infantry-heavy
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconSpear
@@ -166,7 +166,7 @@
   jobName: job-name-infantry-spear
 
 - type: jobIcon
-  locationPreference: Left
+  locationPreference: Right
   parent: JobIcon
   priority: 9
   id: JobIconRanged
@@ -174,3 +174,59 @@
     sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
     state: inf_ranged
   jobName: job-name-infantry-ranged
+
+#squad icons
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad1
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_1
+  jobName: job-name-infantry-ranged
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad2
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_2
+  jobName: job-name-infantry-ranged
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad3
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_3
+  jobName: job-name-infantry-ranged
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad4
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_4
+  jobName: job-name-infantry-ranged
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad5
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_5
+  jobName: job-name-infantry-ranged
+- type: jobIcon
+  locationPreference: Right
+  parent: JobIcon
+  priority: 1
+  id: JobIconSquad6
+  icon:
+    sprite: /Textures/Interface/Misc/civ_hud_squads.rsi
+    state: squad_6
+  jobName: job-name-infantry-ranged

+ 0 - 3
Resources/Prototypes/Entities/Structures/Furniture/beds.yml

@@ -76,9 +76,6 @@
           Poison: -0.2
           Cold: -0.4
           Blunt: -0.2
-    - type: Construction
-      graph: bed
-      node: medicalbed
     - type: GuideHelp
       guides:
         - Medical

+ 4 - 1
Resources/Prototypes/Entities/Structures/Walls/walls.yml

@@ -1376,10 +1376,13 @@
       cost: 6
       delay: 8
       fx: EffectRCDDeconstruct8
+    - type: Damageable
+      damageContainer: StructuralInorganic
+      damageModifierSet: Rock
     - type: Destructible
       thresholds:
         - trigger: !type:DamageTrigger
-            damage: 100
+            damage: 350
           behaviors:
             - !type:DoActsBehavior
               acts: ["Destruction"]

+ 6 - 6
Resources/Prototypes/Roles/Jobs/Civ14/TDM/english.yml

@@ -18,7 +18,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker
@@ -54,7 +54,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker
@@ -91,7 +91,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker
@@ -138,7 +138,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker
@@ -185,7 +185,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker
@@ -259,7 +259,7 @@
         - type: NpcFactionMember
           factions:
             - England
-        - type: ShowEnglishFactionIcons
+        - type: ShowFactionIcons
           factionIcon: EnglishFaction
 
 - type: playTimeTracker

+ 6 - 6
Resources/Prototypes/Roles/Jobs/Civ14/TDM/french.yml

@@ -18,7 +18,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker
@@ -53,7 +53,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker
@@ -90,7 +90,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker
@@ -137,7 +137,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker
@@ -185,7 +185,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker
@@ -259,7 +259,7 @@
         - type: NpcFactionMember
           factions:
             - France
-        - type: ShowFrenchFactionIcons
+        - type: ShowFactionIcons
           factionIcon: FrenchFaction
 
 - type: playTimeTracker

+ 20 - 8
Resources/Prototypes/Roles/Jobs/Civ14/TDM/german.yml

@@ -18,8 +18,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconICpt
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobGermanCaptain
@@ -57,8 +59,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconISgt
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobGermanSergeant
@@ -87,7 +91,7 @@
   playTimeTracker: JobGermanSubmachineGunner
   startingGear: GermanSubmachineGunnerGear
   randomStartingGears: [GermanSubmachineGunnerGear, GermanSubmachineGunnerGear2]
-  icon: "JobIconICpl"
+  icon: "JobIconSoldier"
   supervisors: job-supervisors-cpt
   special:
     - !type:AddComponentSpecial
@@ -96,8 +100,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobGermanSubmachineGunner
@@ -137,7 +143,7 @@
   playTimeTracker: JobGermanRifleman
   startingGear: GermanRiflemanGear
   randomStartingGears: [GermanRiflemanGear, GermanRiflemanGear2]
-  icon: "JobIconICpl"
+  icon: "JobIconSoldier"
   supervisors: job-supervisors-cpt
   special:
     - !type:AddComponentSpecial
@@ -146,8 +152,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobGermanRifleman
@@ -196,8 +204,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobGermanMachinegunner
@@ -245,8 +255,10 @@
         - type: NpcFactionMember
           factions:
             - Germany
-        - type: ShowGermanFactionIcons
+        - type: ShowFactionIcons
           factionIcon: GermanFaction
+          jobIcon: JobIconMedic
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobGermanMedic

+ 20 - 8
Resources/Prototypes/Roles/Jobs/Civ14/TDM/soviet.yml

@@ -18,8 +18,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconICpt
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobSovietCaptain
@@ -56,8 +58,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconISgt
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietSergeant
@@ -86,7 +90,7 @@
   playTimeTracker: JobSovietSubmachineGunner
   startingGear: SovietSubmachineGunnerGear
   randomStartingGears: [SovietSubmachineGunnerGear, SovietSubmachineGunnerGear2]
-  icon: "JobIconICpl"
+  icon: "JobIconSoldier"
   supervisors: job-supervisors-cpt
   special:
     - !type:AddComponentSpecial
@@ -95,8 +99,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietSubmachineGunner
@@ -138,7 +144,7 @@
   playTimeTracker: JobSovietRifleman
   startingGear: SovietRiflemanGear
   randomStartingGears: [SovietRiflemanGear, SovietRiflemanGear2]
-  icon: "JobIconICpl"
+  icon: "JobIconSoldier"
   supervisors: job-supervisors-cpt
   special:
     - !type:AddComponentSpecial
@@ -147,8 +153,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietRifleman
@@ -200,8 +208,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietMachinegunner
@@ -253,8 +263,10 @@
         - type: NpcFactionMember
           factions:
             - Soviet
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconMedic
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobSovietMedic

+ 15 - 5
Resources/Prototypes/Roles/Jobs/Civ14/TDM/sovietCW.yml

@@ -18,8 +18,10 @@
         - type: NpcFactionMember
           factions:
             - SovietCW
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconICpt
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobSovietCWCaptain
@@ -58,8 +60,10 @@
         - type: NpcFactionMember
           factions:
             - SovietCW
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconISgt
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietCWSergeant
@@ -97,8 +101,10 @@
         - type: NpcFactionMember
           factions:
             - SovietCW
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietCWRifleman
@@ -150,8 +156,10 @@
         - type: NpcFactionMember
           factions:
             - SovietCW
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobSovietCWMachinegunner
@@ -203,8 +211,10 @@
         - type: NpcFactionMember
           factions:
             - SovietCW
-        - type: ShowSovietFactionIcons
+        - type: ShowFactionIcons
           factionIcon: SovietFaction
+          jobIcon: JobIconMedic
+          assignSquad: false
 
 - type: playTimeTracker
   id: JobSovietCWMedic

+ 13 - 5
Resources/Prototypes/Roles/Jobs/Civ14/TDM/usa.yml

@@ -17,8 +17,9 @@
         - type: NpcFactionMember
           factions:
             - US
-        - type: ShowUsFactionIcons
+        - type: ShowFactionIcons
           factionIcon: UsFaction
+          jobIcon: JobIconICpt
 
 - type: playTimeTracker
   id: JobUSCaptain
@@ -54,8 +55,10 @@
         - type: NpcFactionMember
           factions:
             - US
-        - type: ShowUsFactionIcons
+        - type: ShowFactionIcons
           factionIcon: UsFaction
+          jobIcon: JobIconISgt
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobUSSergeant
@@ -91,8 +94,10 @@
         - type: NpcFactionMember
           factions:
             - US
-        - type: ShowUsFactionIcons
+        - type: ShowFactionIcons
           factionIcon: UsFaction
+          jobIcon: JobIconRifleman
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobUSRifleman
@@ -142,8 +147,10 @@
         - type: NpcFactionMember
           factions:
             - US
-        - type: ShowUsFactionIcons
+        - type: ShowFactionIcons
           factionIcon: UsFaction
+          jobIcon: JobIconMg
+          assignSquad: true
 
 - type: playTimeTracker
   id: JobUSMachinegunner
@@ -192,8 +199,9 @@
         - type: NpcFactionMember
           factions:
             - US
-        - type: ShowUsFactionIcons
+        - type: ShowFactionIcons
           factionIcon: UsFaction
+          jobIcon: JobIconMedic
 
 - type: playTimeTracker
   id: JobUSMedic

+ 23 - 0
Resources/Textures/Civ14/Objects/medals.rsi/meta.json

@@ -0,0 +1,23 @@
+{
+    "version": 1,
+    "license": "AGPLv3",
+    "copyright": "Taislin",
+    "size": {
+        "x": 32,
+        "y": 32
+    },
+    "states": [
+        {
+            "name": "nomads_bronze",
+            "directions": 1
+        },
+        {
+            "name": "nomads_silver",
+            "directions": 1
+        },
+        {
+            "name": "nomads_gold",
+            "directions": 1
+        }
+    ]
+}

二進制
Resources/Textures/Civ14/Objects/medals.rsi/nomads_bronze.png


二進制
Resources/Textures/Civ14/Objects/medals.rsi/nomads_gold.png


二進制
Resources/Textures/Civ14/Objects/medals.rsi/nomads_silver.png


部分文件因文件數量過多而無法顯示