Преглед на файлове

Medicine improvements (#123)

* healing buffs

* seed fix

* medbook

* medbook fix

* wiki guide

* animal remains
Taislin преди 7 месеца
родител
ревизия
36dbee0e35
променени са 32 файла, в които са добавени 1329 реда и са изтрити 470 реда
  1. 37 0
      Content.Client/MedBook/UI/MedBookBoundUserInterface.cs
  2. 79 0
      Content.Client/MedBook/UI/MedBookWindow.xaml
  3. 257 0
      Content.Client/MedBook/UI/MedBookWindow.xaml.cs
  4. 1 1
      Content.Server/Body/Components/BloodstreamComponent.cs
  5. 21 16
      Content.Server/Botany/Systems/BotanySystem.Seed.cs
  6. 0 12
      Content.Server/Medical/Components/HealthAnalyzerComponent.cs
  7. 52 0
      Content.Server/Medical/Components/MedBookComponent.cs
  8. 0 5
      Content.Server/Medical/HealthAnalyzerSystem.cs
  9. 215 0
      Content.Server/Medical/MedBookSystem.cs
  10. 9 0
      Content.Shared/MedicalScanner/MedBookDoAfterEvent.cs
  11. 28 0
      Content.Shared/MedicalScanner/MedBookScannedUserMessage.cs
  12. 9 0
      Content.Shared/MedicalScanner/MedBookUiKey.cs
  13. 18 2
      Resources/Changelog/Changelog.yml
  14. 5 0
      Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
  15. 5 0
      Resources/Prototypes/Civ14/Entities/Objects/Specific/Farming/plants.yml
  16. 49 0
      Resources/Prototypes/Civ14/Entities/Objects/Tools/medical.yml
  17. 4 4
      Resources/Prototypes/Civ14/Entities/Objects/healing.yml
  18. 121 94
      Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
  19. 326 329
      Resources/Prototypes/Entities/Mobs/Species/base.yml
  20. 29 0
      Resources/Prototypes/Entities/Mobs/Species/human.yml
  21. 1 1
      Resources/Prototypes/Entities/Objects/Materials/ore.yml
  22. 1 0
      Resources/Prototypes/Entities/Objects/Misc/torch.yml
  23. 0 2
      Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
  24. 8 2
      Resources/Prototypes/Entities/Structures/Furniture/beds.yml
  25. 7 0
      Resources/Prototypes/Hydroponics/seeds.yml
  26. 8 0
      Resources/ServerInfo/Guidebook/Medical/Medical.xml
  27. BIN
      Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains1.png
  28. BIN
      Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains2.png
  29. BIN
      Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains3.png
  30. 10 1
      Resources/Textures/Civ14/Objects/tribal.rsi/meta.json
  31. 29 1
      Wiki/src/guides/guide_to_medical.md
  32. BIN
      Wiki/src/images/doctors_handbook.png

+ 37 - 0
Content.Client/MedBook/UI/MedBookBoundUserInterface.cs

@@ -0,0 +1,37 @@
+using Content.Shared.MedicalScanner;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.MedBook.UI
+{
+    [UsedImplicitly]
+    public sealed class MedBookBoundUserInterface : BoundUserInterface
+    {
+        [ViewVariables]
+        private MedBookWindow? _window;
+
+        public MedBookBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+        {
+        }
+
+        protected override void Open()
+        {
+            base.Open();
+
+            _window = this.CreateWindow<MedBookWindow>();
+
+            _window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
+        }
+
+        protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+        {
+            if (_window == null)
+                return;
+
+            if (message is not MedBookScannedUserMessage cast)
+                return;
+
+            _window.Populate(cast);
+        }
+    }
+}

+ 79 - 0
Content.Client/MedBook/UI/MedBookWindow.xaml

@@ -0,0 +1,79 @@
+<controls:FancyWindow
+    xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+    MaxHeight="525"
+    MinWidth="300">
+    <ScrollContainer
+        Margin="5 5 5 5"
+        ReturnMeasure="True"
+        VerticalExpand="True">
+        <BoxContainer
+            Name="RootContainer"
+            VerticalExpand="True"
+            Orientation="Vertical">
+            <Label
+                Name="NoPatientDataText"
+                Text="{Loc health-analyzer-window-no-patient-data-text}"/>
+
+            <BoxContainer
+                Name="PatientDataContainer"
+                Margin="0 0 0 5"
+                Orientation="Vertical">
+                <BoxContainer Orientation="Horizontal"
+                        Margin="0 0 0 5">
+                    <SpriteView OverrideDirection="South"
+                            Scale="2 2"
+                            Name="SpriteView"
+                            Access="Public"
+                            SetSize="64 64"/>
+                    <TextureRect Name="NoDataTex"
+                            Access="Public"
+                            SetSize="64 64"
+                            Visible="false"
+                            Stretch="KeepAspectCentered"
+                            TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
+                    <BoxContainer Margin="5 0 0 0"
+                            Orientation="Vertical"
+                            VerticalAlignment="Top">
+                        <RichTextLabel Name="NameLabel"
+                                SetWidth="150"/>
+                        <Label Name="SpeciesLabel"
+                                VerticalAlignment="Top"
+                                StyleClasses="LabelSubText"/>
+                    </BoxContainer>
+                </BoxContainer>
+
+                <PanelContainer StyleClasses="LowDivider"/>
+
+                <GridContainer Margin="0 5 0 0"
+                        Columns="2">
+                    <Label Text="{Loc 'health-analyzer-window-entity-status-text'}"/>
+                    <Label Name="StatusLabel"/>
+                    <Label Text="{Loc 'health-analyzer-window-entity-blood-level-text'}"/>
+                    <Label Name="BloodLabel"/>
+                </GridContainer>
+            </BoxContainer>
+
+            <PanelContainer Name="AlertsDivider"
+                    Visible="False"
+                    StyleClasses="LowDivider"/>
+
+            <BoxContainer Name="AlertsContainer"
+                    Visible="False"
+                    Margin="0 5"
+                    Orientation="Vertical"
+                    HorizontalAlignment="Center">
+
+            </BoxContainer>
+
+            <PanelContainer StyleClasses="LowDivider"/>
+
+            <BoxContainer
+                Name="GroupsContainer"
+                Margin="0 5 0 5"
+                Orientation="Vertical">
+            </BoxContainer>
+
+        </BoxContainer>
+    </ScrollContainer>
+</controls:FancyWindow>

+ 257 - 0
Content.Client/MedBook/UI/MedBookWindow.xaml.cs

@@ -0,0 +1,257 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.Message;
+using Content.Shared.Atmos;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Alert;
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Inventory;
+using Content.Shared.MedicalScanner;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.MedBook.UI
+{
+    [GenerateTypedNameReferences]
+    public sealed partial class MedBookWindow : FancyWindow
+    {
+        private readonly IEntityManager _entityManager;
+        private readonly SpriteSystem _spriteSystem;
+        private readonly IPrototypeManager _prototypes;
+        private readonly IResourceCache _cache;
+
+        public MedBookWindow()
+        {
+            RobustXamlLoader.Load(this);
+
+            var dependencies = IoCManager.Instance!;
+            _entityManager = dependencies.Resolve<IEntityManager>();
+            _spriteSystem = _entityManager.System<SpriteSystem>();
+            _prototypes = dependencies.Resolve<IPrototypeManager>();
+            _cache = dependencies.Resolve<IResourceCache>();
+        }
+
+        public void Populate(MedBookScannedUserMessage msg)
+        {
+            var target = _entityManager.GetEntity(msg.TargetEntity);
+
+            if (target == null
+                || !_entityManager.TryGetComponent<DamageableComponent>(target, out var damageable))
+            {
+                NoPatientDataText.Visible = true;
+                return;
+            }
+
+            NoPatientDataText.Visible = false;
+
+            // Patient Information
+
+            SpriteView.SetEntity(target.Value);
+            SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
+            NoDataTex.Visible = !SpriteView.Visible;
+
+            var name = new FormattedMessage();
+            name.PushColor(Color.White);
+            name.AddText(_entityManager.HasComponent<MetaDataComponent>(target.Value)
+                ? Identity.Name(target.Value, _entityManager)
+                : Loc.GetString("health-analyzer-window-entity-unknown-text"));
+            NameLabel.SetMessage(name);
+
+            SpeciesLabel.Text =
+                _entityManager.TryGetComponent<HumanoidAppearanceComponent>(target.Value,
+                    out var humanoidAppearanceComponent)
+                    ? Loc.GetString(_prototypes.Index<SpeciesPrototype>(humanoidAppearanceComponent.Species).Name)
+                    : Loc.GetString("health-analyzer-window-entity-unknown-species-text");
+
+            // Basic Diagnostic
+            var parsedBlood = Loc.GetString("medbook-status-normal");
+            BloodLabel.FontColorOverride = Color.Green;
+            if (msg.BloodLevel <= 0.6)
+            {
+                parsedBlood = Loc.GetString("medbook-status-critical");
+                BloodLabel.FontColorOverride = Color.Red;
+            }
+            else if (msg.BloodLevel <= 0.8)
+            {
+                parsedBlood = Loc.GetString("medbook-status-low");
+                BloodLabel.FontColorOverride = Color.Yellow;
+
+            }
+            BloodLabel.Text = !float.IsNaN(msg.BloodLevel)
+                ? parsedBlood
+                : Loc.GetString("health-analyzer-window-entity-unknown-value-text");
+            StatusLabel.Text = Loc.GetString("health-analyzer-window-entity-alive-text");
+            StatusLabel.FontColorOverride = Color.Green;
+            if (_entityManager.TryGetComponent<MobStateComponent>(target.Value, out var mobStateComponent))
+            {
+                if (mobStateComponent.CurrentState == MobState.Critical)
+                {
+                    StatusLabel.FontColorOverride = Color.Yellow;
+                    StatusLabel.Text = Loc.GetString("health-analyzer-window-entity-critical-text");
+                }
+                else if (mobStateComponent.CurrentState == MobState.Dead)
+                {
+                    StatusLabel.FontColorOverride = Color.Red;
+                    StatusLabel.Text = Loc.GetString("health-analyzer-window-entity-dead-text");
+                }
+            }
+
+            // Alerts
+
+            var showAlerts = msg.Unrevivable == true || msg.Bleeding == true;
+
+            AlertsDivider.Visible = showAlerts;
+            AlertsContainer.Visible = showAlerts;
+
+            if (showAlerts)
+                AlertsContainer.DisposeAllChildren();
+
+            if (msg.Unrevivable == true)
+                AlertsContainer.AddChild(new RichTextLabel
+                {
+                    Text = Loc.GetString("health-analyzer-window-entity-unrevivable-text"),
+                    Margin = new Thickness(0, 4),
+                    MaxWidth = 300
+                });
+
+            if (msg.Bleeding == true)
+                AlertsContainer.AddChild(new RichTextLabel
+                {
+                    Text = Loc.GetString("health-analyzer-window-entity-bleeding-text"),
+                    Margin = new Thickness(0, 4),
+                    MaxWidth = 300
+                });
+
+            // Damage Groups
+
+            var damageSortedGroups =
+                damageable.DamagePerGroup.OrderByDescending(damage => damage.Value)
+                    .ToDictionary(x => x.Key, x => x.Value);
+
+            IReadOnlyDictionary<string, FixedPoint2> damagePerType = damageable.Damage.DamageDict;
+
+            DrawDiagnosticGroups(damageSortedGroups, damagePerType);
+        }
+
+        private static string GetStatus(MobState mobState)
+        {
+            return mobState switch
+            {
+                MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"),
+                MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"),
+                MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"),
+                _ => Loc.GetString("health-analyzer-window-entity-unknown-text"),
+            };
+        }
+
+        private void DrawDiagnosticGroups(
+            Dictionary<string, FixedPoint2> groups,
+            IReadOnlyDictionary<string, FixedPoint2> damageDict)
+        {
+            GroupsContainer.RemoveAllChildren();
+
+            foreach (var (damageGroupId, damageAmount) in groups)
+            {
+                if (damageAmount == 0)
+                    continue;
+                var parsedDamage = "None";
+                var color = Color.Green;
+                if (damageAmount > 0 && damageAmount <= 20)
+                {
+                    parsedDamage = "Minimal";
+                    color = Color.Yellow;
+                }
+                else if (damageAmount <= 60)
+                {
+                    parsedDamage = "Medium";
+                    color = Color.Orange;
+                }
+                else if (damageAmount <= 100)
+                {
+                    parsedDamage = "High";
+                    color = Color.Red;
+
+                }
+                else
+                {
+                    parsedDamage = "Extreme";
+                    color = Color.DarkRed;
+                }
+                var groupTitleText = $"{Loc.GetString(
+                    "health-analyzer-window-damage-group-text",
+                    ("damageGroup", _prototypes.Index<DamageGroupPrototype>(damageGroupId).LocalizedName),
+                    ("amount", parsedDamage)
+                )}";
+
+                var groupContainer = new BoxContainer
+                {
+                    Align = BoxContainer.AlignMode.Begin,
+                    Orientation = BoxContainer.LayoutOrientation.Vertical,
+                };
+
+                groupContainer.AddChild(CreateDiagnosticGroupTitle(groupTitleText, damageGroupId, color));
+
+                GroupsContainer.AddChild(groupContainer);
+            }
+        }
+
+        private Texture GetTexture(string texture)
+        {
+            var rsiPath = new ResPath("/Textures/Objects/Devices/health_analyzer.rsi");
+            var rsiSprite = new SpriteSpecifier.Rsi(rsiPath, texture);
+
+            var rsi = _cache.GetResource<RSIResource>(rsiSprite.RsiPath).RSI;
+            if (!rsi.TryGetState(rsiSprite.RsiState, out var state))
+            {
+                rsiSprite = new SpriteSpecifier.Rsi(rsiPath, "unknown");
+            }
+
+            return _spriteSystem.Frame0(rsiSprite);
+        }
+
+        private static Label CreateDiagnosticItemLabel(string text, Color color)
+        {
+            return new Label
+            {
+                Text = text,
+                FontColorOverride = color
+            };
+        }
+
+        private BoxContainer CreateDiagnosticGroupTitle(string text, string id, Color color)
+        {
+            var rootContainer = new BoxContainer
+            {
+                Margin = new Thickness(0, 6, 0, 0),
+                VerticalAlignment = VAlignment.Bottom,
+                Orientation = BoxContainer.LayoutOrientation.Horizontal,
+            };
+
+            rootContainer.AddChild(new TextureRect
+            {
+                SetSize = new Vector2(30, 30),
+                Texture = GetTexture(id.ToLower())
+            });
+
+            rootContainer.AddChild(CreateDiagnosticItemLabel(text, color));
+
+            return rootContainer;
+        }
+    }
+}

+ 1 - 1
Content.Server/Body/Components/BloodstreamComponent.cs

@@ -60,7 +60,7 @@ public sealed partial class BloodstreamComponent : Component
         ///     What percentage of current blood is necessary to avoid dealing blood loss damage?
         ///     What percentage of current blood is necessary to avoid dealing blood loss damage?
         /// </summary>
         /// </summary>
         [DataField]
         [DataField]
-        public float BloodlossThreshold = 0.9f;
+        public float BloodlossThreshold = 0.75f;
 
 
         /// <summary>
         /// <summary>
         ///     The base bloodloss damage to be incurred if below <see cref="BloodlossThreshold"/>
         ///     The base bloodloss damage to be incurred if below <see cref="BloodlossThreshold"/>

+ 21 - 16
Content.Server/Botany/Systems/BotanySystem.Seed.cs

@@ -14,6 +14,8 @@
 using Robust.Shared.Random;
 using Robust.Shared.Random;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq;
+using Content.Shared.Coordinates; // Added for Log
+using Robust.Shared.Utility; // Added for FormattedMessage
 
 
 namespace Content.Server.Botany.Systems;
 namespace Content.Server.Botany.Systems;
 
 
@@ -149,30 +151,33 @@ public IEnumerable<EntityUid> GenerateProduct(SeedData proto, EntityCoordinates
 
 
         var products = new List<EntityUid>();
         var products = new List<EntityUid>();
 
 
-        if (totalYield > 1 || proto.HarvestRepeat != HarvestType.NoRepeat)
+        // If yield is > 1 OR there's more than one product type OR harvest repeats, it's not unique.
+        if (totalYield > 1 || proto.ProductPrototypes.Count > 1 || proto.HarvestRepeat != HarvestType.NoRepeat)
             proto.Unique = false;
             proto.Unique = false;
 
 
         for (var i = 0; i < totalYield; i++)
         for (var i = 0; i < totalYield; i++)
         {
         {
-            var product = _robustRandom.Pick(proto.ProductPrototypes);
-
-            var entity = Spawn(product, position);
-            _randomHelper.RandomOffset(entity, 0.25f);
-            products.Add(entity);
+            // Iterate through ALL product prototypes defined in the seed data
+            foreach (var productPrototypeId in proto.ProductPrototypes)
+            {
+                var entity = Spawn(productPrototypeId, position);
+                _randomHelper.RandomOffset(entity, 0.25f);
+                products.Add(entity);
 
 
-            var produce = EnsureComp<ProduceComponent>(entity);
+                var produce = EnsureComp<ProduceComponent>(entity);
 
 
-            produce.Seed = proto;
-            ProduceGrown(entity, produce);
+                produce.Seed = proto;
+                ProduceGrown(entity, produce); // Apply specific produce logic (like solutions)
 
 
-            _appearance.SetData(entity, ProduceVisuals.Potency, proto.Potency);
+                _appearance.SetData(entity, ProduceVisuals.Potency, proto.Potency);
 
 
-            if (proto.Mysterious)
-            {
-                var metaData = MetaData(entity);
-                _metaData.SetEntityName(entity, metaData.EntityName + "?", metaData);
-                _metaData.SetEntityDescription(entity,
-                    metaData.EntityDescription + " " + Loc.GetString("botany-mysterious-description-addon"), metaData);
+                if (proto.Mysterious)
+                {
+                    var metaData = MetaData(entity);
+                    _metaData.SetEntityName(entity, metaData.EntityName + "?", metaData);
+                    _metaData.SetEntityDescription(entity,
+                        metaData.EntityDescription + " " + Loc.GetString("botany-mysterious-description-addon"), metaData);
+                }
             }
             }
         }
         }
 
 

+ 0 - 12
Content.Server/Medical/Components/HealthAnalyzerComponent.cs

@@ -44,18 +44,6 @@ public sealed partial class HealthAnalyzerComponent : Component
     [DataField]
     [DataField]
     public float MaxScanRange = 2.5f;
     public float MaxScanRange = 2.5f;
 
 
-    /// <summary>
-    /// Sound played on scanning begin
-    /// </summary>
-    [DataField]
-    public SoundSpecifier? ScanningBeginSound;
-
-    /// <summary>
-    /// Sound played on scanning end
-    /// </summary>
-    [DataField]
-    public SoundSpecifier ScanningEndSound = new SoundPathSpecifier("/Audio/Items/Medical/healthscanner.ogg");
-
     /// <summary>
     /// <summary>
     /// Whether to show up the popup
     /// Whether to show up the popup
     /// </summary>
     /// </summary>

+ 52 - 0
Content.Server/Medical/Components/MedBookComponent.cs

@@ -0,0 +1,52 @@
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Medical.Components;
+
+/// <summary>
+/// After scanning, retrieves the target Uid to use with its related UI.
+/// </summary>
+/// <remarks>
+/// Requires <c>ItemToggleComponent</c>.
+/// </remarks>
+[RegisterComponent, AutoGenerateComponentPause]
+[Access(typeof(MedBookSystem), typeof(CryoPodSystem))]
+public sealed partial class MedBookComponent : Component
+{
+    /// <summary>
+    /// When should the next update be sent for the patient
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoPausedField]
+    public TimeSpan NextUpdate = TimeSpan.Zero;
+
+    /// <summary>
+    /// The delay between patient health updates
+    /// </summary>
+    [DataField]
+    public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
+
+    /// <summary>
+    /// How long it takes to scan someone.
+    /// </summary>
+    [DataField]
+    public TimeSpan ScanDelay = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// Which entity has been scanned, for continuous updates
+    /// </summary>
+    [DataField]
+    public EntityUid? ScannedEntity;
+
+    /// <summary>
+    /// The maximum range in tiles at which the analyzer can receive continuous updates
+    /// </summary>
+    [DataField]
+    public float MaxScanRange = 1.5f;
+
+    /// <summary>
+    /// Whether to show up the popup
+    /// </summary>
+    [DataField]
+    public bool Silent;
+}

+ 0 - 5
Content.Server/Medical/HealthAnalyzerSystem.cs

@@ -83,8 +83,6 @@ private void OnAfterInteract(Entity<HealthAnalyzerComponent> uid, ref AfterInter
         if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target) || !_cell.HasDrawCharge(uid, user: args.User))
         if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target) || !_cell.HasDrawCharge(uid, user: args.User))
             return;
             return;
 
 
-        _audio.PlayPvs(uid.Comp.ScanningBeginSound, uid);
-
         var doAfterCancelled = !_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.ScanDelay, new HealthAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid)
         var doAfterCancelled = !_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.ScanDelay, new HealthAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid)
         {
         {
             NeedHand = true,
             NeedHand = true,
@@ -103,9 +101,6 @@ private void OnDoAfter(Entity<HealthAnalyzerComponent> uid, ref HealthAnalyzerDo
         if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
         if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
             return;
             return;
 
 
-        if (!uid.Comp.Silent)
-            _audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
-
         OpenUserInterface(args.User, uid);
         OpenUserInterface(args.User, uid);
         BeginAnalyzingEntity(uid, args.Target.Value);
         BeginAnalyzingEntity(uid, args.Target.Value);
         args.Handled = true;
         args.Handled = true;

+ 215 - 0
Content.Server/Medical/MedBookSystem.cs

@@ -0,0 +1,215 @@
+using Content.Server.Body.Components;
+using Content.Server.Medical.Components;
+using Content.Server.PowerCell;
+using Content.Server.Temperature.Components;
+using Content.Shared.Traits.Assorted;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Damage;
+using Content.Shared.DoAfter;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.MedicalScanner;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Popups;
+using Robust.Server.GameObjects;
+using Robust.Shared.Containers;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Medical;
+
+public sealed class MedBookSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly PowerCellSystem _cell = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly ItemToggleSystem _toggle = default!;
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+    [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+    [Dependency] private readonly TransformSystem _transformSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<MedBookComponent, AfterInteractEvent>(OnAfterInteract);
+        SubscribeLocalEvent<MedBookComponent, MedBookDoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<MedBookComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
+        SubscribeLocalEvent<MedBookComponent, ItemToggledEvent>(OnToggled);
+        SubscribeLocalEvent<MedBookComponent, DroppedEvent>(OnDropped);
+    }
+
+    public override void Update(float frameTime)
+    {
+        var analyzerQuery = EntityQueryEnumerator<MedBookComponent, TransformComponent>();
+        while (analyzerQuery.MoveNext(out var uid, out var component, out var transform))
+        {
+            //Update rate limited to 1 second
+            if (component.NextUpdate > _timing.CurTime)
+                continue;
+
+            if (component.ScannedEntity is not { } patient)
+                continue;
+
+            if (Deleted(patient))
+            {
+                StopAnalyzingEntity((uid, component), patient);
+                continue;
+            }
+
+            component.NextUpdate = _timing.CurTime + component.UpdateInterval;
+
+            //Get distance between health analyzer and the scanned entity
+            var patientCoordinates = Transform(patient).Coordinates;
+            if (!_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange))
+            {
+                //Range too far, disable updates
+                StopAnalyzingEntity((uid, component), patient);
+                continue;
+            }
+
+            UpdateScannedUser(uid, patient, true);
+        }
+    }
+
+    /// <summary>
+    /// Trigger the doafter for scanning
+    /// </summary>
+    private void OnAfterInteract(Entity<MedBookComponent> uid, ref AfterInteractEvent args)
+    {
+        if (args.Target == null || !args.CanReach || !HasComp<MobStateComponent>(args.Target) || !_cell.HasDrawCharge(uid, user: args.User))
+            return;
+
+        var doAfterCancelled = !_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.ScanDelay, new MedBookDoAfterEvent(), uid, target: args.Target, used: uid)
+        {
+            NeedHand = true,
+            BreakOnMove = true,
+        });
+
+        if (args.Target == args.User || doAfterCancelled || uid.Comp.Silent)
+            return;
+
+        var msg = Loc.GetString("medbook-popup-scan-target", ("user", Identity.Entity(args.User, EntityManager)));
+        _popupSystem.PopupEntity(msg, args.Target.Value, args.Target.Value, PopupType.Medium);
+    }
+
+    private void OnDoAfter(Entity<MedBookComponent> uid, ref MedBookDoAfterEvent args)
+    {
+        if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
+            return;
+
+        OpenUserInterface(args.User, uid);
+        BeginAnalyzingEntity(uid, args.Target.Value);
+        args.Handled = true;
+    }
+
+    /// <summary>
+    /// Turn off when placed into a storage item or moved between slots/hands
+    /// </summary>
+    private void OnInsertedIntoContainer(Entity<MedBookComponent> uid, ref EntGotInsertedIntoContainerMessage args)
+    {
+        if (uid.Comp.ScannedEntity is { } patient)
+            _toggle.TryDeactivate(uid.Owner);
+    }
+
+    /// <summary>
+    /// Disable continuous updates once turned off
+    /// </summary>
+    private void OnToggled(Entity<MedBookComponent> ent, ref ItemToggledEvent args)
+    {
+        if (!args.Activated && ent.Comp.ScannedEntity is { } patient)
+            StopAnalyzingEntity(ent, patient);
+    }
+
+    /// <summary>
+    /// Turn off the analyser when dropped
+    /// </summary>
+    private void OnDropped(Entity<MedBookComponent> uid, ref DroppedEvent args)
+    {
+        if (uid.Comp.ScannedEntity is { } patient)
+            _toggle.TryDeactivate(uid.Owner);
+    }
+
+    private void OpenUserInterface(EntityUid user, EntityUid analyzer)
+    {
+        if (!_uiSystem.HasUi(analyzer, MedBookUiKey.Key))
+            return;
+
+        _uiSystem.OpenUi(analyzer, MedBookUiKey.Key, user);
+    }
+
+    /// <summary>
+    /// Mark the entity as having its health analyzed, and link the analyzer to it
+    /// </summary>
+    /// <param name="medBook">The health analyzer that should receive the updates</param>
+    /// <param name="target">The entity to start analyzing</param>
+    private void BeginAnalyzingEntity(Entity<MedBookComponent> medBook, EntityUid target)
+    {
+        //Link the health analyzer to the scanned entity
+        medBook.Comp.ScannedEntity = target;
+
+        _toggle.TryActivate(medBook.Owner);
+
+        UpdateScannedUser(medBook, target, true);
+    }
+
+    /// <summary>
+    /// Remove the analyzer from the active list, and remove the component if it has no active analyzers
+    /// </summary>
+    /// <param name="medBook">The health analyzer that's receiving the updates</param>
+    /// <param name="target">The entity to analyze</param>
+    private void StopAnalyzingEntity(Entity<MedBookComponent> medBook, EntityUid target)
+    {
+        //Unlink the analyzer
+        medBook.Comp.ScannedEntity = null;
+
+        _toggle.TryDeactivate(medBook.Owner);
+
+        UpdateScannedUser(medBook, target, false);
+    }
+
+    /// <summary>
+    /// Send an update for the target to the medBook
+    /// </summary>
+    /// <param name="medBook">The health analyzer</param>
+    /// <param name="target">The entity being scanned</param>
+    /// <param name="scanMode">True makes the UI show ACTIVE, False makes the UI show INACTIVE</param>
+    public void UpdateScannedUser(EntityUid medBook, EntityUid target, bool scanMode)
+    {
+        if (!_uiSystem.HasUi(medBook, MedBookUiKey.Key))
+            return;
+
+        if (!HasComp<DamageableComponent>(target))
+            return;
+
+        var bodyTemperature = float.NaN;
+
+        if (TryComp<TemperatureComponent>(target, out var temp))
+            bodyTemperature = temp.CurrentTemperature;
+
+        var bloodAmount = float.NaN;
+        var bleeding = false;
+        var unrevivable = false;
+
+        if (TryComp<BloodstreamComponent>(target, out var bloodstream) &&
+            _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName,
+                ref bloodstream.BloodSolution, out var bloodSolution))
+        {
+            bloodAmount = bloodSolution.FillFraction;
+            bleeding = bloodstream.BleedAmount > 0;
+        }
+
+        if (TryComp<UnrevivableComponent>(target, out var unrevivableComp) && unrevivableComp.Analyzable)
+            unrevivable = true;
+
+        _uiSystem.ServerSendUiMessage(medBook, MedBookUiKey.Key, new MedBookScannedUserMessage(
+            GetNetEntity(target),
+            bodyTemperature,
+            bloodAmount,
+            scanMode,
+            bleeding,
+            unrevivable
+        ));
+    }
+}

+ 9 - 0
Content.Shared/MedicalScanner/MedBookDoAfterEvent.cs

@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.MedicalScanner;
+
+[Serializable, NetSerializable]
+public sealed partial class MedBookDoAfterEvent : SimpleDoAfterEvent
+{
+}

+ 28 - 0
Content.Shared/MedicalScanner/MedBookScannedUserMessage.cs

@@ -0,0 +1,28 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.MedicalScanner;
+
+/// <summary>
+///     On interacting with an entity retrieves the entity UID for use with getting the current damage of the mob.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class MedBookScannedUserMessage : BoundUserInterfaceMessage
+{
+    public readonly NetEntity? TargetEntity;
+    public float Temperature;
+    public float BloodLevel;
+    public bool? ScanMode;
+    public bool? Bleeding;
+    public bool? Unrevivable;
+
+    public MedBookScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable)
+    {
+        TargetEntity = targetEntity;
+        Temperature = temperature;
+        BloodLevel = bloodLevel;
+        ScanMode = scanMode;
+        Bleeding = bleeding;
+        Unrevivable = unrevivable;
+    }
+}
+

+ 9 - 0
Content.Shared/MedicalScanner/MedBookUiKey.cs

@@ -0,0 +1,9 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.MedicalScanner;
+
+[Serializable, NetSerializable]
+public enum MedBookUiKey : byte
+{
+    Key
+}

+ 18 - 2
Resources/Changelog/Changelog.yml

@@ -71,7 +71,7 @@ Entries:
         type: Add
         type: Add
       - message: Added deep water to the middle of the rivers.
       - message: Added deep water to the middle of the rivers.
         type: Add
         type: Add
-    id: 5
+    id: 6
     time: "2025-04-16T00:00:00.0000000+00:00"
     time: "2025-04-16T00:00:00.0000000+00:00"
     url: https://github.com/Civ13/Civ14/pull/118
     url: https://github.com/Civ13/Civ14/pull/118
 
 
@@ -85,6 +85,22 @@ Entries:
         type: Add
         type: Add
       - message: Wolves and Bears now drop leather as well as pelts.
       - message: Wolves and Bears now drop leather as well as pelts.
         type: Fix
         type: Fix
-    id: 5
+    id: 7
     time: "2025-04-17T00:00:00.0000000+00:00"
     time: "2025-04-17T00:00:00.0000000+00:00"
     url: https://github.com/Civ13/Civ14/pull/121
     url: https://github.com/Civ13/Civ14/pull/121
+
+  - author: Taislin
+    changes:
+      - message: Adds the doctor's handbook as a simplified health analyser.
+        type: Add
+      - message: Buffed bed healing rates.
+        type: Tweak
+      - message: Reduced threshold of current blood to inflict bloodloss damage.
+        type: Tweak
+      - message: Buffed healing effect from herbs.
+        type: Tweak
+      - message: Bodies now decay after a while.
+        type: Add
+    id: 8
+    time: "2025-04-18T00:00:00.0000000+00:00"
+    url: https://github.com/Civ13/Civ14/pull/123

+ 5 - 0
Resources/Locale/en-US/medical/components/health-analyzer-component.ftl

@@ -23,3 +23,8 @@ health-analyzer-window-scan-mode-active = Active
 health-analyzer-window-scan-mode-inactive = Inactive
 health-analyzer-window-scan-mode-inactive = Inactive
 
 
 health-analyzer-popup-scan-target = {CAPITALIZE(THE($user))} is trying to scan you!
 health-analyzer-popup-scan-target = {CAPITALIZE(THE($user))} is trying to scan you!
+
+medbook-popup-scan-target = {CAPITALIZE(THE($user))} is trying to examine you!
+medbook-status-critical = Critical
+medbook-status-low = Low
+medbook-status-normal = Normal

+ 5 - 0
Resources/Prototypes/Civ14/Entities/Objects/Specific/Farming/plants.yml

@@ -7,6 +7,7 @@
   packetPrototype: HealingHerbsSeedsCiv
   packetPrototype: HealingHerbsSeedsCiv
   productPrototypes:
   productPrototypes:
     - HealingHerbs
     - HealingHerbs
+    - HealingHerbsSeedsCiv
   lifespan: 25
   lifespan: 25
   maturation: 10
   maturation: 10
   production: 3
   production: 3
@@ -33,6 +34,7 @@
   packetPrototype: ComfreySeedsCiv
   packetPrototype: ComfreySeedsCiv
   productPrototypes:
   productPrototypes:
     - ComfreyHealingHerbs
     - ComfreyHealingHerbs
+    - ComfreySeedsCiv
   lifespan: 20
   lifespan: 20
   maturation: 8
   maturation: 8
   production: 3
   production: 3
@@ -55,6 +57,7 @@
   packetPrototype: YarrowSeedsCiv
   packetPrototype: YarrowSeedsCiv
   productPrototypes:
   productPrototypes:
     - YarrowHealingHerbs
     - YarrowHealingHerbs
+    - YarrowSeedsCiv
   lifespan: 20
   lifespan: 20
   maturation: 8
   maturation: 8
   production: 3
   production: 3
@@ -77,6 +80,7 @@
   packetPrototype: ElderflowerSeedsCiv
   packetPrototype: ElderflowerSeedsCiv
   productPrototypes:
   productPrototypes:
     - ElderflowerHealingHerbs
     - ElderflowerHealingHerbs
+    - ElderflowerSeedsCiv
   lifespan: 22
   lifespan: 22
   maturation: 10
   maturation: 10
   production: 3
   production: 3
@@ -99,6 +103,7 @@
   packetPrototype: MilkThistleSeedsCiv
   packetPrototype: MilkThistleSeedsCiv
   productPrototypes:
   productPrototypes:
     - MilkThistleHealingHerbs
     - MilkThistleHealingHerbs
+    - MilkThistleSeedsCiv
   lifespan: 15
   lifespan: 15
   maturation: 6
   maturation: 6
   production: 3
   production: 3

+ 49 - 0
Resources/Prototypes/Civ14/Entities/Objects/Tools/medical.yml

@@ -0,0 +1,49 @@
+- type: entity
+  id: DoctorsHandbook
+  parent: BaseItem
+  name: "doctor's handbook"
+  description: A medical guidebook that helps you diagnose patients.
+  components:
+    - type: Sprite
+      sprite: Civ14/Objects/library.rsi
+      state: bookmed
+    - type: ActivatableUI
+      key: enum.MedBookUiKey.Key
+    - type: UserInterface
+      interfaces:
+        enum.MedBookUiKey.Key:
+          type: MedBookBoundUserInterface
+    - type: MedBook
+    - type: Appearance
+    - type: Construction
+      graph: DoctorsHandbook
+      node: end
+      agemin: 0
+      agemax: 8
+
+- type: construction
+  name: "doctor's handbook"
+  id: DoctorsHandbook
+  graph: DoctorsHandbook
+  startNode: start
+  targetNode: end
+  category: construction-category-misc
+  description: A medical guidebook that helps you diagnose patients.
+  icon: { sprite: Civ14/Objects/library.rsi, state: bookmed }
+  objectType: Item
+  agemin: 0
+  agemax: 8
+
+- type: constructionGraph
+  id: DoctorsHandbook
+  start: start
+  graph:
+    - node: start
+      edges:
+        - to: end
+          steps:
+            - material: WoodPlank
+              amount: 20
+              doAfter: 15
+    - node: end
+      entity: DoctorsHandbook

+ 4 - 4
Resources/Prototypes/Civ14/Entities/Objects/healing.yml

@@ -53,7 +53,7 @@
         - Biological
         - Biological
       damage:
       damage:
         types:
         types:
-          Bloodloss: -10
+          Bloodloss: -24
       healingBeginSound:
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
       healingEndSound:
@@ -83,7 +83,7 @@
         - Biological
         - Biological
       damage:
       damage:
         groups:
         groups:
-          Brute: -10
+          Brute: -18
       healingBeginSound:
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
       healingEndSound:
@@ -113,7 +113,7 @@
         - Biological
         - Biological
       damage:
       damage:
         types:
         types:
-          Poison: -10
+          Poison: -12
       healingBeginSound:
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
       healingEndSound:
@@ -143,7 +143,7 @@
         - Biological
         - Biological
       damage:
       damage:
         types:
         types:
-          Asphyxiation: -8
+          Asphyxiation: -16
       healingBeginSound:
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
       healingEndSound:

+ 121 - 94
Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml

@@ -1,115 +1,142 @@
 - type: entity
 - type: entity
   save: false
   save: false
   parent:
   parent:
-  - BaseMob
-  - MobDamageable
-  - MobPolymorphable
-  - MobAtmosExposed
+    - BaseMob
+    - MobDamageable
+    - MobPolymorphable
+    - MobAtmosExposed
   id: BaseSimpleMob
   id: BaseSimpleMob
   suffix: AI
   suffix: AI
   abstract: true
   abstract: true
   components:
   components:
-  - type: Reactive
-    groups:
-      Flammable: [Touch]
-      Extinguish: [Touch]
-      Acidic: [Touch, Ingestion]
-  - type: Internals
-  - type: MovementSpeedModifier
-    baseWalkSpeed : 4
-    baseSprintSpeed : 4
-  - type: StatusEffects
-    allowed:
-    - SlowedDown
-    - Stutter
-    - Electrocution
-    - ForcedSleep
-    - TemporaryBlindness
-    - Pacified
-    - Flashed
-    - RadiationProtection
-    - Drowsiness
-    - Adrenaline
-  - type: Buckle
-  - type: StandingState
-  - type: Tag
-    tags:
-    - DoorBumpOpener
+    - type: Reactive
+      groups:
+        Flammable: [Touch]
+        Extinguish: [Touch]
+        Acidic: [Touch, Ingestion]
+    - type: Internals
+    - type: MovementSpeedModifier
+      baseWalkSpeed: 4
+      baseSprintSpeed: 4
+    - type: StatusEffects
+      allowed:
+        - SlowedDown
+        - Stutter
+        - Electrocution
+        - ForcedSleep
+        - TemporaryBlindness
+        - Pacified
+        - Flashed
+        - RadiationProtection
+        - Drowsiness
+        - Adrenaline
+    - type: Buckle
+    - type: StandingState
+    - type: Tag
+      tags:
+        - DoorBumpOpener
 
 
 - type: entity
 - type: entity
   abstract: true
   abstract: true
   parent:
   parent:
-  - BaseSimpleMob
-  - MobCombat
-  - MobBloodstream
-  - MobFlammable
+    - BaseSimpleMob
+    - MobCombat
+    - MobBloodstream
+    - MobFlammable
   id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
   id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
   suffix: AI
   suffix: AI
   components:
   components:
-  - type: NpcFactionMember
-    factions:
-    - SimpleNeutral
-  - type: HTN
-    rootTask:
-      task: IdleCompound
-  - type: MeleeWeapon
-    angle: 0
-    animation: WeaponArcBite
-  - type: Body
-    prototype: Animal
-  - type: Climbing
-  - type: NameIdentifier
-    group: GenericNumber
-  - type: SlowOnDamage
-    speedModifierThresholds:
-      60: 0.7
-      80: 0.5
-  - type: MobPrice
-    price: 1000 # Living critters are valuable in space.
-  - type: Perishable
+    - type: NpcFactionMember
+      factions:
+        - SimpleNeutral
+    - type: HTN
+      rootTask:
+        task: IdleCompound
+    - type: MeleeWeapon
+      angle: 0
+      animation: WeaponArcBite
+    - type: Body
+      prototype: Animal
+    - type: Climbing
+    - type: NameIdentifier
+      group: GenericNumber
+    - type: SlowOnDamage
+      speedModifierThresholds:
+        60: 0.7
+        80: 0.5
+    - type: MobPrice
+      price: 1000 # Living critters are valuable in space.
+    - type: Perishable
 
 
 - type: entity
 - type: entity
   parent:
   parent:
-  - MobRespirator
-  - MobAtmosStandard
-  - SimpleSpaceMobBase
+    - MobRespirator
+    - MobAtmosStandard
+    - SimpleSpaceMobBase
   id: SimpleMobBase # for air breathers
   id: SimpleMobBase # for air breathers
   suffix: AI
   suffix: AI
   abstract: true
   abstract: true
   components:
   components:
-  - type: Hunger
-    thresholds: # only animals and rats are derived from this prototype so let's override it here and in rats' proto
-      Overfed: 100
-      Okay: 50
-      Peckish: 25
-      Starving: 10
-      Dead: 0
-    baseDecayRate: 0.00925925925926 # it is okay for animals to eat and drink less than humans, but more frequently
-  - type: Thirst
-    thresholds:
-      OverHydrated: 200
-      Okay: 150
-      Thirsty: 100
-      Parched: 50
-      Dead: 0
-    baseDecayRate: 0.04
-  - type: StatusEffects
-    allowed:
-    - Stun
-    - KnockedDown
-    - SlowedDown
-    - Stutter
-    - Electrocution
-    - ForcedSleep
-    - TemporaryBlindness
-    - Pacified
-    - StaminaModifier
-    - Flashed
-    - RadiationProtection
-    - Drowsiness
-    - Adrenaline
-  - type: Bloodstream
-    bloodMaxVolume: 150
-  - type: MobPrice
-    price: 150
-  - type: FloatingVisuals
+    - type: Hunger
+      thresholds: # only animals and rats are derived from this prototype so let's override it here and in rats' proto
+        Overfed: 100
+        Okay: 50
+        Peckish: 25
+        Starving: 10
+        Dead: 0
+      baseDecayRate: 0.00925925925926 # it is okay for animals to eat and drink less than humans, but more frequently
+    - type: Thirst
+      thresholds:
+        OverHydrated: 200
+        Okay: 150
+        Thirsty: 100
+        Parched: 50
+        Dead: 0
+      baseDecayRate: 0.04
+    - type: RotInto
+      entity: AnimalRemains
+      stage: 2
+    - type: StatusEffects
+      allowed:
+        - Stun
+        - KnockedDown
+        - SlowedDown
+        - Stutter
+        - Electrocution
+        - ForcedSleep
+        - TemporaryBlindness
+        - Pacified
+        - StaminaModifier
+        - Flashed
+        - RadiationProtection
+        - Drowsiness
+        - Adrenaline
+    - type: Bloodstream
+      bloodMaxVolume: 150
+    - type: MobPrice
+      price: 150
+    - type: FloatingVisuals
+
+- type: entity
+  name: animal remains
+  parent: BaseItem
+  id: AnimalRemains
+  description: The remains of a long dead animal.
+  components:
+    - type: Sprite
+      sprite: Civ14/Objects/tribal.rsi
+      state: animal_remains1
+      layers:
+        - state: animal_remains1
+          map: ["random"]
+    - type: RandomSprite
+      available:
+        - random:
+            animal_remains1: ""
+            animal_remains2: ""
+            animal_remains3: ""
+    - type: Item
+      size: Huge
+    - type: Tag
+      tags:
+        - Trash

+ 326 - 329
Resources/Prototypes/Entities/Mobs/Species/base.yml

@@ -1,286 +1,283 @@
 - type: entity
 - type: entity
   save: false
   save: false
   parent:
   parent:
-  - BaseMob
-  - MobDamageable
-  - MobPolymorphable
-  - MobCombat
-  - StripableInventoryBase
+    - BaseMob
+    - MobDamageable
+    - MobPolymorphable
+    - MobCombat
+    - StripableInventoryBase
   id: BaseMobSpecies
   id: BaseMobSpecies
   abstract: true
   abstract: true
   components:
   components:
-  - type: Sprite
-    layers:
-    - map: [ "enum.HumanoidVisualLayers.Chest" ]
-    - map: [ "enum.HumanoidVisualLayers.Head" ]
-    - map: [ "enum.HumanoidVisualLayers.Snout" ]
-    - map: [ "enum.HumanoidVisualLayers.Eyes" ]
-    - map: [ "enum.HumanoidVisualLayers.RArm" ]
-    - map: [ "enum.HumanoidVisualLayers.LArm" ]
-    - map: [ "enum.HumanoidVisualLayers.RLeg" ]
-    - map: [ "enum.HumanoidVisualLayers.LLeg" ]
-    - map: [ "enum.HumanoidVisualLayers.UndergarmentBottom" ]
-    - map: [ "enum.HumanoidVisualLayers.UndergarmentTop" ]
-    - map: ["jumpsuit"]
-    - map: ["enum.HumanoidVisualLayers.LFoot"]
-    - map: ["enum.HumanoidVisualLayers.RFoot"]
-    - map: ["enum.HumanoidVisualLayers.LHand"]
-    - map: ["enum.HumanoidVisualLayers.RHand"]
-    - map: [ "gloves" ]
-    - map: [ "shoes" ]
-    - map: [ "ears" ]
-    - map: [ "eyes" ]
-    - map: [ "belt" ]
-    - map: [ "id" ]
-    - map: [ "outerClothing" ]
-    - map: [ "back" ]
-    - map: [ "neck" ]
-    - map: [ "enum.HumanoidVisualLayers.FacialHair" ]
-    - map: [ "enum.HumanoidVisualLayers.Hair" ]
-    - map: [ "enum.HumanoidVisualLayers.HeadSide" ]
-    - map: [ "enum.HumanoidVisualLayers.HeadTop" ]
-    - map: [ "enum.HumanoidVisualLayers.Tail" ]
-    - map: [ "mask" ]
-    - map: [ "head" ]
-    - map: [ "pocket1" ]
-    - map: [ "pocket2" ]
-    - map: ["enum.HumanoidVisualLayers.Handcuffs"]
-      color: "#ffffff"
-      sprite: Objects/Misc/handcuffs.rsi
-      state: body-overlay-2
-      visible: false
-    - map: [ "clownedon" ] # Dynamically generated
-      sprite: "Effects/creampie.rsi"
-      state: "creampie_human"
-      visible: false
-  - type: DamageVisuals
-    thresholds: [ 10, 20, 30, 50, 70, 100 ]
-    targetLayers:
-    - "enum.HumanoidVisualLayers.Chest"
-    - "enum.HumanoidVisualLayers.Head"
-    - "enum.HumanoidVisualLayers.LArm"
-    - "enum.HumanoidVisualLayers.LLeg"
-    - "enum.HumanoidVisualLayers.RArm"
-    - "enum.HumanoidVisualLayers.RLeg"
-    damageOverlayGroups:
-      Brute:
-        sprite: Mobs/Effects/brute_damage.rsi
-        color: "#FF0000"
-      Burn:
-        sprite: Mobs/Effects/burn_damage.rsi
-  - type: GenericVisualizer
-    visuals:
-      enum.CreamPiedVisuals.Creamed:
-        clownedon: # Not 'creampied' bc I can already see Skyrat complaining about conflicts.
-          True: {visible: true}
-          False: {visible: false}
-  - type: StatusIcon
-    bounds: -0.5,-0.5,0.5,0.5
-  - type: RotationVisuals
-    defaultRotation: 90
-    horizontalRotation: 90
-  - type: HumanoidAppearance
-    species: Human
-  - type: SlowOnDamage
-    speedModifierThresholds:
-      60: 0.7
-      80: 0.5
-  - type: Fixtures
-    fixtures: # TODO: This needs a second fixture just for mob collisions.
-      fix1:
-        shape:
-          !type:PhysShapeCircle
-          radius: 0.35
-        density: 185
-        restitution: 0.0
-        mask:
-        - MobMask
-        layer:
-        - MobLayer
-  - type: FloorOcclusion
-  - type: RangedDamageSound
-    soundGroups:
-      Brute:
-        collection:
-          MeatBulletImpact
-    soundTypes:
-      Heat:
-        collection:
-          MeatLaserImpact
-  - type: Reactive
-    groups:
-      Flammable: [ Touch ]
-      Extinguish: [ Touch ]
-      Acidic: [Touch, Ingestion]
-    reactions:
-    - reagents: [Water, SpaceCleaner]
-      methods: [Touch]
-      effects:
-      - !type:WashCreamPieReaction
-  - type: StatusEffects
-    allowed:
-    - Stun
-    - KnockedDown
-    - SlowedDown
-    - Stutter
-    - SeeingRainbows
-    - Electrocution
-    - Drunk
-    - SlurredSpeech
-    - RatvarianLanguage
-    - PressureImmunity
-    - Muted
-    - ForcedSleep
-    - TemporaryBlindness
-    - Pacified
-    - StaminaModifier
-    - Flashed
-    - RadiationProtection
-    - Drowsiness
-    - Adrenaline
-  - type: Body
-    prototype: Human
-    requiredLegs: 2
-  - type: Identity
-  - type: IdExaminable
-  - type: Hands
-  - type: ComplexInteraction
-  - type: Internals
-  - type: FloatingVisuals
-  - type: Climbing
-  - type: Cuffable
-  - type: Ensnareable
-    sprite: Objects/Misc/ensnare.rsi
-    state: icon
-  - type: AnimationPlayer
-  - type: Buckle
-  - type: CombatMode
-    canDisarm: true
-  - type: MeleeWeapon
-    soundHit:
-      collection: Punch
-    angle: 30
-    animation: WeaponArcFist
-    attackRate: 1
-    damage:
-      types:
-        Blunt: 5
-  - type: SleepEmitSound
-  - type: SSDIndicator
-  - type: StandingState
-  - type: Dna
-  - type: MindContainer
-    showExamineInfo: true
-  - type: CanEnterCryostorage
-  - type: InteractionPopup
-    successChance: 1
-    interactSuccessString: hugging-success-generic
-    interactSuccessSound: /Audio/Effects/thudswoosh.ogg
-    messagePerceivedByOthers: hugging-success-generic-others
-  - type: CanHostGuardian
-  - type: NpcFactionMember
-    factions:
-    - Nomads
-  - type: CreamPied
-  - type: Stripping
-  - type: UserInterface
-    interfaces:
-      enum.HumanoidMarkingModifierKey.Key:
-        type: HumanoidMarkingModifierBoundUserInterface
-      enum.StrippingUiKey.Key:
-        type: StrippableBoundUserInterface
-  - type: Puller
-  - type: Speech
-    speechSounds: Alto
-  - type: DamageForceSay
-  - type: Vocal
-    sounds:
-      Male: MaleHuman
-      Female: FemaleHuman
-      Unsexed: MaleHuman
-  - type: Emoting
-  - type: BodyEmotes
-    soundsId: GeneralBodyEmotes
-  - type: Grammar
-    attributes:
-      proper: true
-  - type: MobPrice
-    price: 1500 # Kidnapping a living person and selling them for cred is a good move.
-    deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less.
-  - type: Tag
-    tags:
-    - CanPilot
-    - FootstepSound
-    - DoorBumpOpener
-    - AnomalyHost
+    - type: Sprite
+      layers:
+        - map: ["enum.HumanoidVisualLayers.Chest"]
+        - map: ["enum.HumanoidVisualLayers.Head"]
+        - map: ["enum.HumanoidVisualLayers.Snout"]
+        - map: ["enum.HumanoidVisualLayers.Eyes"]
+        - map: ["enum.HumanoidVisualLayers.RArm"]
+        - map: ["enum.HumanoidVisualLayers.LArm"]
+        - map: ["enum.HumanoidVisualLayers.RLeg"]
+        - map: ["enum.HumanoidVisualLayers.LLeg"]
+        - map: ["enum.HumanoidVisualLayers.UndergarmentBottom"]
+        - map: ["enum.HumanoidVisualLayers.UndergarmentTop"]
+        - map: ["jumpsuit"]
+        - map: ["enum.HumanoidVisualLayers.LFoot"]
+        - map: ["enum.HumanoidVisualLayers.RFoot"]
+        - map: ["enum.HumanoidVisualLayers.LHand"]
+        - map: ["enum.HumanoidVisualLayers.RHand"]
+        - map: ["gloves"]
+        - map: ["shoes"]
+        - map: ["ears"]
+        - map: ["eyes"]
+        - map: ["belt"]
+        - map: ["id"]
+        - map: ["outerClothing"]
+        - map: ["back"]
+        - map: ["neck"]
+        - map: ["enum.HumanoidVisualLayers.FacialHair"]
+        - map: ["enum.HumanoidVisualLayers.Hair"]
+        - map: ["enum.HumanoidVisualLayers.HeadSide"]
+        - map: ["enum.HumanoidVisualLayers.HeadTop"]
+        - map: ["enum.HumanoidVisualLayers.Tail"]
+        - map: ["mask"]
+        - map: ["head"]
+        - map: ["pocket1"]
+        - map: ["pocket2"]
+        - map: ["enum.HumanoidVisualLayers.Handcuffs"]
+          color: "#ffffff"
+          sprite: Objects/Misc/handcuffs.rsi
+          state: body-overlay-2
+          visible: false
+        - map: ["clownedon"] # Dynamically generated
+          sprite: "Effects/creampie.rsi"
+          state: "creampie_human"
+          visible: false
+    - type: DamageVisuals
+      thresholds: [10, 20, 30, 50, 70, 100]
+      targetLayers:
+        - "enum.HumanoidVisualLayers.Chest"
+        - "enum.HumanoidVisualLayers.Head"
+        - "enum.HumanoidVisualLayers.LArm"
+        - "enum.HumanoidVisualLayers.LLeg"
+        - "enum.HumanoidVisualLayers.RArm"
+        - "enum.HumanoidVisualLayers.RLeg"
+      damageOverlayGroups:
+        Brute:
+          sprite: Mobs/Effects/brute_damage.rsi
+          color: "#FF0000"
+        Burn:
+          sprite: Mobs/Effects/burn_damage.rsi
+    - type: GenericVisualizer
+      visuals:
+        enum.CreamPiedVisuals.Creamed:
+          clownedon: # Not 'creampied' bc I can already see Skyrat complaining about conflicts.
+            True: { visible: true }
+            False: { visible: false }
+    - type: StatusIcon
+      bounds: -0.5,-0.5,0.5,0.5
+    - type: RotationVisuals
+      defaultRotation: 90
+      horizontalRotation: 90
+    - type: HumanoidAppearance
+      species: Human
+    - type: SlowOnDamage
+      speedModifierThresholds:
+        60: 0.7
+        80: 0.5
+    - type: Fixtures
+      fixtures: # TODO: This needs a second fixture just for mob collisions.
+        fix1:
+          shape: !type:PhysShapeCircle
+            radius: 0.35
+          density: 185
+          restitution: 0.0
+          mask:
+            - MobMask
+          layer:
+            - MobLayer
+    - type: FloorOcclusion
+    - type: RangedDamageSound
+      soundGroups:
+        Brute:
+          collection: MeatBulletImpact
+      soundTypes:
+        Heat:
+          collection: MeatLaserImpact
+    - type: Reactive
+      groups:
+        Flammable: [Touch]
+        Extinguish: [Touch]
+        Acidic: [Touch, Ingestion]
+      reactions:
+        - reagents: [Water, SpaceCleaner]
+          methods: [Touch]
+          effects:
+            - !type:WashCreamPieReaction
+    - type: StatusEffects
+      allowed:
+        - Stun
+        - KnockedDown
+        - SlowedDown
+        - Stutter
+        - SeeingRainbows
+        - Electrocution
+        - Drunk
+        - SlurredSpeech
+        - RatvarianLanguage
+        - PressureImmunity
+        - Muted
+        - ForcedSleep
+        - TemporaryBlindness
+        - Pacified
+        - StaminaModifier
+        - Flashed
+        - RadiationProtection
+        - Drowsiness
+        - Adrenaline
+    - type: Body
+      prototype: Human
+      requiredLegs: 2
+    - type: Identity
+    - type: IdExaminable
+    - type: Hands
+    - type: ComplexInteraction
+    - type: Internals
+    - type: FloatingVisuals
+    - type: Climbing
+    - type: Cuffable
+    - type: Ensnareable
+      sprite: Objects/Misc/ensnare.rsi
+      state: icon
+    - type: AnimationPlayer
+    - type: Buckle
+    - type: CombatMode
+      canDisarm: true
+    - type: MeleeWeapon
+      soundHit:
+        collection: Punch
+      angle: 30
+      animation: WeaponArcFist
+      attackRate: 1
+      damage:
+        types:
+          Blunt: 5
+    - type: SleepEmitSound
+    - type: SSDIndicator
+    - type: StandingState
+    - type: Dna
+    - type: MindContainer
+      showExamineInfo: true
+    - type: CanEnterCryostorage
+    - type: InteractionPopup
+      successChance: 1
+      interactSuccessString: hugging-success-generic
+      interactSuccessSound: /Audio/Effects/thudswoosh.ogg
+      messagePerceivedByOthers: hugging-success-generic-others
+    - type: CanHostGuardian
+    - type: NpcFactionMember
+      factions:
+        - Nomads
+    - type: CreamPied
+    - type: Stripping
+    - type: UserInterface
+      interfaces:
+        enum.HumanoidMarkingModifierKey.Key:
+          type: HumanoidMarkingModifierBoundUserInterface
+        enum.StrippingUiKey.Key:
+          type: StrippableBoundUserInterface
+    - type: Puller
+    - type: Speech
+      speechSounds: Alto
+    - type: DamageForceSay
+    - type: Vocal
+      sounds:
+        Male: MaleHuman
+        Female: FemaleHuman
+        Unsexed: MaleHuman
+    - type: Emoting
+    - type: BodyEmotes
+      soundsId: GeneralBodyEmotes
+    - type: Grammar
+      attributes:
+        proper: true
+    - type: MobPrice
+      price: 1500 # Kidnapping a living person and selling them for cred is a good move.
+      deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less.
+    - type: Tag
+      tags:
+        - CanPilot
+        - FootstepSound
+        - DoorBumpOpener
+        - AnomalyHost
 
 
 - type: entity
 - type: entity
   save: false
   save: false
   parent:
   parent:
-  - MobBloodstream
-  - MobRespirator
-  - MobAtmosStandard
-  - MobFlammable
-  - BaseMobSpecies
+    - MobBloodstream
+    - MobRespirator
+    - MobAtmosStandard
+    - MobFlammable
+    - BaseMobSpecies
   id: BaseMobSpeciesOrganic
   id: BaseMobSpeciesOrganic
   abstract: true
   abstract: true
   components:
   components:
-  - type: Barotrauma
-    damage:
-      types:
-        Blunt: 0.50 #per second, scales with pressure and other constants.
-        Heat: 0.1
-  - type: PassiveDamage # Slight passive regen. Assuming one damage type, comes out to about 4 damage a minute.
-    allowedStates:
-    - Alive
-    damageCap: 20
-    damage:
-      types:
-        Heat: -0.07
-      groups:
-        Brute: -0.07
-  - type: Fingerprint
-  - type: Blindable
-  # Other
-  - type: Temperature
-    heatDamageThreshold: 325
-    coldDamageThreshold: 260
-    currentTemperature: 310.15
-    specificHeat: 42
-    coldDamage:
-      types:
-        Cold: 0.1 #per second, scales with temperature & other constants
-    heatDamage:
-      types:
-        Heat: 1.5 #per second, scales with temperature & other constants
-  - type: TemperatureSpeed
-    thresholds:
-      293: 0.8
-      280: 0.6
-      260: 0.4
-  - type: ThermalRegulator
-    metabolismHeat: 800
-    radiatedHeat: 100
-    implicitHeatRegulation: 500
-    sweatHeatRegulation: 2000
-    shiveringHeatRegulation: 2000
-    normalBodyTemperature: 310.15
-    thermalRegulationTemperatureThreshold: 25
-  - type: Perishable
-  - type: Butcherable
-    butcheringType: Spike # TODO human.
-    spawned:
-      - id: FoodMeat
-        amount: 5
-  - type: Respirator
-    damage:
-      types:
-        Asphyxiation: 1.0
-    damageRecovery:
-      types:
-        Asphyxiation: -1.0
-  - type: FireVisuals
-    alternateState: Standing
+    - type: Barotrauma
+      damage:
+        types:
+          Blunt: 0.50 #per second, scales with pressure and other constants.
+          Heat: 0.1
+    - type: PassiveDamage # Slight passive regen. Assuming one damage type, comes out to about 4 damage a minute.
+      allowedStates:
+        - Alive
+      damageCap: 20
+      damage:
+        types:
+          Heat: -0.07
+        groups:
+          Brute: -0.07
+    - type: Fingerprint
+    - type: Blindable
+    # Other
+    - type: Temperature
+      heatDamageThreshold: 325
+      coldDamageThreshold: 260
+      currentTemperature: 310.15
+      specificHeat: 42
+      coldDamage:
+        types:
+          Cold: 0.1 #per second, scales with temperature & other constants
+      heatDamage:
+        types:
+          Heat: 1.5 #per second, scales with temperature & other constants
+    - type: TemperatureSpeed
+      thresholds:
+        293: 0.8
+        280: 0.6
+        260: 0.4
+    - type: ThermalRegulator
+      metabolismHeat: 800
+      radiatedHeat: 100
+      implicitHeatRegulation: 500
+      sweatHeatRegulation: 2000
+      shiveringHeatRegulation: 2000
+      normalBodyTemperature: 310.15
+      thermalRegulationTemperatureThreshold: 25
+    - type: Perishable
+    - type: Butcherable
+      butcheringType: Spike # TODO human.
+      spawned:
+        - id: FoodMeat
+          amount: 5
+    - type: Respirator
+      damage:
+        types:
+          Asphyxiation: 1.0
+      damageRecovery:
+        types:
+          Asphyxiation: -1.0
+    - type: FireVisuals
+      alternateState: Standing
 
 
 - type: entity
 - type: entity
   save: false
   save: false
@@ -288,62 +285,62 @@
   parent: InventoryBase
   parent: InventoryBase
   abstract: true
   abstract: true
   components:
   components:
-  - type: Hands
-  - type: ComplexInteraction
-  - type: ContainerContainer
-  - type: Icon
-    sprite: Mobs/Species/Human/parts.rsi
-    state: full
-  - type: Sprite
-    drawdepth: Mobs
-    noRot: true
-    # TODO BODY Turn these into individual body parts?
-    layers:
-    - map: [ "enum.HumanoidVisualLayers.Chest" ]
-    - map: [ "enum.HumanoidVisualLayers.Head" ]
-    - map: [ "enum.HumanoidVisualLayers.Snout" ]
-    - map: [ "enum.HumanoidVisualLayers.Eyes" ]
-    - map: [ "enum.HumanoidVisualLayers.RArm" ]
-    - map: [ "enum.HumanoidVisualLayers.LArm" ]
-    - map: [ "enum.HumanoidVisualLayers.RLeg" ]
-    - map: [ "enum.HumanoidVisualLayers.LLeg" ]
-    - map: [ "enum.HumanoidVisualLayers.UndergarmentBottom" ]
-    - map: [ "enum.HumanoidVisualLayers.UndergarmentTop" ]
-    - map: ["jumpsuit"]
-    - map: ["enum.HumanoidVisualLayers.LFoot"]
-    - map: ["enum.HumanoidVisualLayers.RFoot"]
-    - map: ["enum.HumanoidVisualLayers.LHand"]
-    - map: ["enum.HumanoidVisualLayers.RHand"]
-    - map: ["enum.HumanoidVisualLayers.Handcuffs"]
-      color: "#ffffff"
-      sprite: Objects/Misc/handcuffs.rsi
-      state: body-overlay-2
-      visible: false
-    - map: [ "gloves" ]
-    - map: [ "shoes" ]
-    - map: [ "ears" ]
-    - map: [ "eyes" ]
-    - map: [ "belt" ]
-    - map: [ "id" ]
-    - map: [ "outerClothing" ]
-    - map: [ "back" ]
-    - map: [ "neck" ]
-    - map: [ "enum.HumanoidVisualLayers.FacialHair" ]
-    - map: [ "enum.HumanoidVisualLayers.Hair" ]
-    - map: [ "enum.HumanoidVisualLayers.HeadSide" ]
-    - map: [ "enum.HumanoidVisualLayers.HeadTop" ]
-    - map: [ "enum.HumanoidVisualLayers.Tail" ]
-    - map: [ "mask" ]
-    - map: [ "head" ]
-    - map: [ "pocket1" ]
-    - map: [ "pocket2" ]
-  - type: Appearance
-  - type: HumanoidAppearance
-    species: Human
-  - type: Body
-    prototype: Human
-    requiredLegs: 2
-  - type: UserInterface
-    interfaces:
-      enum.HumanoidMarkingModifierKey.Key: # sure, this can go here too
-        type: HumanoidMarkingModifierBoundUserInterface
+    - type: Hands
+    - type: ComplexInteraction
+    - type: ContainerContainer
+    - type: Icon
+      sprite: Mobs/Species/Human/parts.rsi
+      state: full
+    - type: Sprite
+      drawdepth: Mobs
+      noRot: true
+      # TODO BODY Turn these into individual body parts?
+      layers:
+        - map: ["enum.HumanoidVisualLayers.Chest"]
+        - map: ["enum.HumanoidVisualLayers.Head"]
+        - map: ["enum.HumanoidVisualLayers.Snout"]
+        - map: ["enum.HumanoidVisualLayers.Eyes"]
+        - map: ["enum.HumanoidVisualLayers.RArm"]
+        - map: ["enum.HumanoidVisualLayers.LArm"]
+        - map: ["enum.HumanoidVisualLayers.RLeg"]
+        - map: ["enum.HumanoidVisualLayers.LLeg"]
+        - map: ["enum.HumanoidVisualLayers.UndergarmentBottom"]
+        - map: ["enum.HumanoidVisualLayers.UndergarmentTop"]
+        - map: ["jumpsuit"]
+        - map: ["enum.HumanoidVisualLayers.LFoot"]
+        - map: ["enum.HumanoidVisualLayers.RFoot"]
+        - map: ["enum.HumanoidVisualLayers.LHand"]
+        - map: ["enum.HumanoidVisualLayers.RHand"]
+        - map: ["enum.HumanoidVisualLayers.Handcuffs"]
+          color: "#ffffff"
+          sprite: Objects/Misc/handcuffs.rsi
+          state: body-overlay-2
+          visible: false
+        - map: ["gloves"]
+        - map: ["shoes"]
+        - map: ["ears"]
+        - map: ["eyes"]
+        - map: ["belt"]
+        - map: ["id"]
+        - map: ["outerClothing"]
+        - map: ["back"]
+        - map: ["neck"]
+        - map: ["enum.HumanoidVisualLayers.FacialHair"]
+        - map: ["enum.HumanoidVisualLayers.Hair"]
+        - map: ["enum.HumanoidVisualLayers.HeadSide"]
+        - map: ["enum.HumanoidVisualLayers.HeadTop"]
+        - map: ["enum.HumanoidVisualLayers.Tail"]
+        - map: ["mask"]
+        - map: ["head"]
+        - map: ["pocket1"]
+        - map: ["pocket2"]
+    - type: Appearance
+    - type: HumanoidAppearance
+      species: Human
+    - type: Body
+      prototype: Human
+      requiredLegs: 2
+    - type: UserInterface
+      interfaces:
+        enum.HumanoidMarkingModifierKey.Key: # sure, this can go here too
+          type: HumanoidMarkingModifierBoundUserInterface

+ 29 - 0
Resources/Prototypes/Entities/Mobs/Species/human.yml

@@ -10,6 +10,9 @@
       sprite: Mobs/Species/Human/parts.rsi
       sprite: Mobs/Species/Human/parts.rsi
       state: full
       state: full
     - type: Thirst
     - type: Thirst
+    - type: RotInto
+      entity: HumanRemains
+      stage: 2
     - type: Butcherable
     - type: Butcherable
       butcheringType: Spike
       butcheringType: Spike
       spawned:
       spawned:
@@ -42,3 +45,29 @@
             32:
             32:
               sprite: Mobs/Species/Human/displacement.rsi
               sprite: Mobs/Species/Human/displacement.rsi
               state: jumpsuit-female
               state: jumpsuit-female
+- type: entity
+  name: human remains
+  parent: BaseItem
+  id: HumanRemains
+  description: The remains of some poor person.
+  components:
+    - type: Sprite
+      sprite: Civ14/Objects/tribal.rsi
+      state: remains1
+      layers:
+        - state: remains1
+          map: ["random"]
+    - type: RandomSprite
+      available:
+        - random:
+            remains1: ""
+            remains2: ""
+            remains3: ""
+            remains4: ""
+            remains5: ""
+            remains6: ""
+    - type: Item
+      size: Huge
+    - type: Tag
+      tags:
+        - Trash

+ 1 - 1
Resources/Prototypes/Entities/Objects/Materials/ore.yml

@@ -345,7 +345,7 @@
     - type: Item
     - type: Item
       heldPrefix: coal
       heldPrefix: coal
     - type: BurnFuel
     - type: BurnFuel
-      burnTime: 4
+      burnTime: 5
 
 
 - type: entity
 - type: entity
   parent: Coal
   parent: Coal

+ 1 - 0
Resources/Prototypes/Entities/Objects/Misc/torch.yml

@@ -81,3 +81,4 @@
     - type: Tag
     - type: Tag
       tags:
       tags:
         - Torch
         - Torch
+        - Burnable

+ 0 - 2
Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml

@@ -25,8 +25,6 @@
     - type: ItemToggle
     - type: ItemToggle
       onUse: false
       onUse: false
     - type: HealthAnalyzer
     - type: HealthAnalyzer
-      scanningEndSound:
-        path: "/Audio/Items/Medical/healthscanner.ogg"
     - type: Tag
     - type: Tag
       tags:
       tags:
         - DiscreteHealthAnalyzer
         - DiscreteHealthAnalyzer

+ 8 - 2
Resources/Prototypes/Entities/Structures/Furniture/beds.yml

@@ -7,8 +7,14 @@
     - type: HealOnBuckle
     - type: HealOnBuckle
       damage:
       damage:
         types:
         types:
-          Poison: -0.1
-          Blunt: -0.1
+          Poison: -0.2
+          Cold: -0.1
+          Shock: -0.1
+          Blunt: -0.2
+          Bloodloss: -0.1
+          Asphyxiation: -0.1
+        groups:
+          Brute: -0.1
     - type: Physics
     - type: Physics
       bodyType: Static
       bodyType: Static
     - type: Fixtures
     - type: Fixtures

+ 7 - 0
Resources/Prototypes/Hydroponics/seeds.yml

@@ -7,6 +7,7 @@
   packetPrototype: WheatSeedsCiv
   packetPrototype: WheatSeedsCiv
   productPrototypes:
   productPrototypes:
     - WheatBushel
     - WheatBushel
+    - WheatSeedsCiv
   mutationPrototypes:
   mutationPrototypes:
     - meatwheat
     - meatwheat
   lifespan: 25
   lifespan: 25
@@ -35,6 +36,7 @@
   packetPrototype: HempSeedsCiv
   packetPrototype: HempSeedsCiv
   productPrototypes:
   productPrototypes:
     - MaterialRope1
     - MaterialRope1
+    - HempSeedsCiv
   lifespan: 25
   lifespan: 25
   maturation: 6
   maturation: 6
   production: 3
   production: 3
@@ -397,6 +399,7 @@
   packetPrototype: PotatoSeedsCiv
   packetPrototype: PotatoSeedsCiv
   productPrototypes:
   productPrototypes:
     - FoodPotato
     - FoodPotato
+    - PotatoSeedsCiv
   lifespan: 30
   lifespan: 30
   maturation: 10
   maturation: 10
   production: 3
   production: 3
@@ -812,6 +815,7 @@
   packetPrototype: CornSeedsCiv
   packetPrototype: CornSeedsCiv
   productPrototypes:
   productPrototypes:
     - FoodCorn
     - FoodCorn
+    - CornSeedsCiv
   lifespan: 25
   lifespan: 25
   maturation: 8
   maturation: 8
   production: 6
   production: 6
@@ -1171,6 +1175,7 @@
   packetPrototype: PoppySeeds
   packetPrototype: PoppySeeds
   productPrototypes:
   productPrototypes:
     - FoodPoppy
     - FoodPoppy
+    - PoppySeeds
   mutationPrototypes:
   mutationPrototypes:
     - lily
     - lily
   lifespan: 25
   lifespan: 25
@@ -1199,6 +1204,7 @@
   packetPrototype: AloeSeeds
   packetPrototype: AloeSeeds
   productPrototypes:
   productPrototypes:
     - FoodAloe
     - FoodAloe
+    - AloeSeeds
   lifespan: 25
   lifespan: 25
   maturation: 10
   maturation: 10
   production: 3
   production: 3
@@ -1507,6 +1513,7 @@
   packetPrototype: RiceSeedsCiv
   packetPrototype: RiceSeedsCiv
   productPrototypes:
   productPrototypes:
     - RiceBushel
     - RiceBushel
+    - RiceSeedsCiv
   lifespan: 25
   lifespan: 25
   maturation: 6
   maturation: 6
   production: 3
   production: 3

+ 8 - 0
Resources/ServerInfo/Guidebook/Medical/Medical.xml

@@ -5,6 +5,14 @@ In the Stone age, most of the medicine you will find is restricted to bandages a
 
 
 <GuideEntityEmbed Entity="GauzeLeather" /> You can craft bandages from [bold]cloth[/bold] and [bold]leather[/bold]. They will help you stop bleeding.
 <GuideEntityEmbed Entity="GauzeLeather" /> You can craft bandages from [bold]cloth[/bold] and [bold]leather[/bold]. They will help you stop bleeding.
 
 
+## Doctor's Handbook
+
+<GuideEntityEmbed Entity="DoctorsHandbook" />
+
+The doctor's handbook is a tool that can be used to help you diagnose patients. It will give you a rough overview of their status, and any damages they might have.
+
+## Medicinal Herbs
+
 You can find medicinal herbs growing wild around the map. If you harvest them, you will also get some seeds so you can plant them yourself.
 You can find medicinal herbs growing wild around the map. If you harvest them, you will also get some seeds so you can plant them yourself.
 
 
 <GuideEntityEmbed Entity="WildPlantElderflower" /><GuideEntityEmbed Entity="ElderflowerHealingHerbs" />
 <GuideEntityEmbed Entity="WildPlantElderflower" /><GuideEntityEmbed Entity="ElderflowerHealingHerbs" />

BIN
Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains1.png


BIN
Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains2.png


BIN
Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains3.png


+ 10 - 1
Resources/Textures/Civ14/Objects/tribal.rsi/meta.json

@@ -46,6 +46,15 @@
         },
         },
         {
         {
             "name": "remains6"
             "name": "remains6"
+        },
+        {
+            "name": "animal_remains1"
+        },
+        {
+            "name": "animal_remains2"
+        },
+        {
+            "name": "animal_remains3"
         }
         }
     ]
     ]
-}
+}

+ 29 - 1
Wiki/src/guides/guide_to_medical.md

@@ -1,3 +1,31 @@
 # Guide to Medical
 # Guide to Medical
 
 
-Coming soon!
+## First Aid
+
+![leather bandage](https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/surgery.rsi/leatherbandage.png)![cloth bandage](https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/surgery.rsi/bint.png) You can craft bandages from **cloth** or **leather**. They will help you stop bleeding.
+
+```admonish tip
+You should carry bandages with you at all times. Theres nothing worse than bleeding to death when trying to return to your house after a successfull hunt.
+```
+
+## Doctor's Handbook
+
+![doctors handbook](./../images/doctors_handbook.png)
+
+The doctor's handbook is a tool that can be used to help you diagnose patients. It will give you a rough overview of their status, and any damages they might have.
+
+## Hedicinal Hrbs
+
+You can find medicinal herbs growing wild around the map. If you harvest them, you will also get some seeds so you can plant them yourself - see the [guide to farming](guide_to_farming.md).
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/Farming/elderflower.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/Farming/elderflower.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Elderflower** has a long history in European folk medicine for treating colds and sinus issues. It reduces asphyxiation by clearing airways and boosting oxygen intake.
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/Farming/yarrow.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Civ14/Objects/Farming/yarrow.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Yarrow** has been used since ancient times as a hemostatic (blood-clotting) agent. It's known for its astringent properties that help staunch bleeding.
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/spacemans_trumpet.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/spacemans_trumpet.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Comfrey** has been used historically to heal bruises, sprains, and fractures due to its anti-inflammatory and tissue-repair properties. Heals brute damage.
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/galaxythistle.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/galaxythistle.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Milk Thistle** has long been used as a liver tonic, with silymarin compounds believed to detoxify and protect against poisons.
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/poppy.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/poppy.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Poppy** is the source of opium and morphine, known for sedative and pain-relieving effects, but it can also stabilize shock in small doses by calming the nervous system. Historically, it's been used for pain and trauma management.
+
+<img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/aloe.rsi/harvest.png" style="width:64px;height:64px;image-rendering: pixelated;"><img src="https://raw.githubusercontent.com/Civ13/Civ14/refs/heads/master/Resources/Textures/Objects/Specific/Hydroponics/aloe.rsi/produce.png" style="width:64px;height:64px;image-rendering: pixelated;"> **Aloe vera**'s gel is a well-documented remedy for burns, soothing inflammation and promoting skin healing.

BIN
Wiki/src/images/doctors_handbook.png