浏览代码

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. 二进制
      Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains1.png
  28. 二进制
      Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains2.png
  29. 二进制
      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. 二进制
      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?
         /// </summary>
         [DataField]
-        public float BloodlossThreshold = 0.9f;
+        public float BloodlossThreshold = 0.75f;
 
         /// <summary>
         ///     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 System.Diagnostics.CodeAnalysis;
 using System.Linq;
+using Content.Shared.Coordinates; // Added for Log
+using Robust.Shared.Utility; // Added for FormattedMessage
 
 namespace Content.Server.Botany.Systems;
 
@@ -149,30 +151,33 @@ public IEnumerable<EntityUid> GenerateProduct(SeedData proto, EntityCoordinates
 
         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;
 
         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]
     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>
     /// Whether to show up the popup
     /// </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))
             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)
         {
             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))
             return;
 
-        if (!uid.Comp.Silent)
-            _audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
-
         OpenUserInterface(args.User, uid);
         BeginAnalyzingEntity(uid, args.Target.Value);
         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
       - message: Added deep water to the middle of the rivers.
         type: Add
-    id: 5
+    id: 6
     time: "2025-04-16T00:00:00.0000000+00:00"
     url: https://github.com/Civ13/Civ14/pull/118
 
@@ -85,6 +85,22 @@ Entries:
         type: Add
       - message: Wolves and Bears now drop leather as well as pelts.
         type: Fix
-    id: 5
+    id: 7
     time: "2025-04-17T00:00:00.0000000+00:00"
     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-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
   productPrototypes:
     - HealingHerbs
+    - HealingHerbsSeedsCiv
   lifespan: 25
   maturation: 10
   production: 3
@@ -33,6 +34,7 @@
   packetPrototype: ComfreySeedsCiv
   productPrototypes:
     - ComfreyHealingHerbs
+    - ComfreySeedsCiv
   lifespan: 20
   maturation: 8
   production: 3
@@ -55,6 +57,7 @@
   packetPrototype: YarrowSeedsCiv
   productPrototypes:
     - YarrowHealingHerbs
+    - YarrowSeedsCiv
   lifespan: 20
   maturation: 8
   production: 3
@@ -77,6 +80,7 @@
   packetPrototype: ElderflowerSeedsCiv
   productPrototypes:
     - ElderflowerHealingHerbs
+    - ElderflowerSeedsCiv
   lifespan: 22
   maturation: 10
   production: 3
@@ -99,6 +103,7 @@
   packetPrototype: MilkThistleSeedsCiv
   productPrototypes:
     - MilkThistleHealingHerbs
+    - MilkThistleSeedsCiv
   lifespan: 15
   maturation: 6
   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
       damage:
         types:
-          Bloodloss: -10
+          Bloodloss: -24
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
@@ -83,7 +83,7 @@
         - Biological
       damage:
         groups:
-          Brute: -10
+          Brute: -18
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
@@ -113,7 +113,7 @@
         - Biological
       damage:
         types:
-          Poison: -10
+          Poison: -12
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:
@@ -143,7 +143,7 @@
         - Biological
       damage:
         types:
-          Asphyxiation: -8
+          Asphyxiation: -16
       healingBeginSound:
         path: "/Audio/Items/Medical/ointment_begin.ogg"
       healingEndSound:

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

@@ -1,115 +1,142 @@
 - type: entity
   save: false
   parent:
-  - BaseMob
-  - MobDamageable
-  - MobPolymorphable
-  - MobAtmosExposed
+    - BaseMob
+    - MobDamageable
+    - MobPolymorphable
+    - MobAtmosExposed
   id: BaseSimpleMob
   suffix: AI
   abstract: true
   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
   abstract: true
   parent:
-  - BaseSimpleMob
-  - MobCombat
-  - MobBloodstream
-  - MobFlammable
+    - BaseSimpleMob
+    - MobCombat
+    - MobBloodstream
+    - MobFlammable
   id: SimpleSpaceMobBase # Mob without barotrauma, freezing and asphyxiation (for space carps!?)
   suffix: AI
   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
   parent:
-  - MobRespirator
-  - MobAtmosStandard
-  - SimpleSpaceMobBase
+    - MobRespirator
+    - MobAtmosStandard
+    - SimpleSpaceMobBase
   id: SimpleMobBase # for air breathers
   suffix: AI
   abstract: true
   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
   save: false
   parent:
-  - BaseMob
-  - MobDamageable
-  - MobPolymorphable
-  - MobCombat
-  - StripableInventoryBase
+    - BaseMob
+    - MobDamageable
+    - MobPolymorphable
+    - MobCombat
+    - StripableInventoryBase
   id: BaseMobSpecies
   abstract: true
   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
   save: false
   parent:
-  - MobBloodstream
-  - MobRespirator
-  - MobAtmosStandard
-  - MobFlammable
-  - BaseMobSpecies
+    - MobBloodstream
+    - MobRespirator
+    - MobAtmosStandard
+    - MobFlammable
+    - BaseMobSpecies
   id: BaseMobSpeciesOrganic
   abstract: true
   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
   save: false
@@ -288,62 +285,62 @@
   parent: InventoryBase
   abstract: true
   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
       state: full
     - type: Thirst
+    - type: RotInto
+      entity: HumanRemains
+      stage: 2
     - type: Butcherable
       butcheringType: Spike
       spawned:
@@ -42,3 +45,29 @@
             32:
               sprite: Mobs/Species/Human/displacement.rsi
               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
       heldPrefix: coal
     - type: BurnFuel
-      burnTime: 4
+      burnTime: 5
 
 - type: entity
   parent: Coal

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

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

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

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

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

@@ -7,8 +7,14 @@
     - type: HealOnBuckle
       damage:
         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
       bodyType: Static
     - type: Fixtures

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

@@ -7,6 +7,7 @@
   packetPrototype: WheatSeedsCiv
   productPrototypes:
     - WheatBushel
+    - WheatSeedsCiv
   mutationPrototypes:
     - meatwheat
   lifespan: 25
@@ -35,6 +36,7 @@
   packetPrototype: HempSeedsCiv
   productPrototypes:
     - MaterialRope1
+    - HempSeedsCiv
   lifespan: 25
   maturation: 6
   production: 3
@@ -397,6 +399,7 @@
   packetPrototype: PotatoSeedsCiv
   productPrototypes:
     - FoodPotato
+    - PotatoSeedsCiv
   lifespan: 30
   maturation: 10
   production: 3
@@ -812,6 +815,7 @@
   packetPrototype: CornSeedsCiv
   productPrototypes:
     - FoodCorn
+    - CornSeedsCiv
   lifespan: 25
   maturation: 8
   production: 6
@@ -1171,6 +1175,7 @@
   packetPrototype: PoppySeeds
   productPrototypes:
     - FoodPoppy
+    - PoppySeeds
   mutationPrototypes:
     - lily
   lifespan: 25
@@ -1199,6 +1204,7 @@
   packetPrototype: AloeSeeds
   productPrototypes:
     - FoodAloe
+    - AloeSeeds
   lifespan: 25
   maturation: 10
   production: 3
@@ -1507,6 +1513,7 @@
   packetPrototype: RiceSeedsCiv
   productPrototypes:
     - RiceBushel
+    - RiceSeedsCiv
   lifespan: 25
   maturation: 6
   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.
 
+## 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.
 
 <GuideEntityEmbed Entity="WildPlantElderflower" /><GuideEntityEmbed Entity="ElderflowerHealingHerbs" />

二进制
Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains1.png


二进制
Resources/Textures/Civ14/Objects/tribal.rsi/animal_remains2.png


二进制
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": "animal_remains1"
+        },
+        {
+            "name": "animal_remains2"
+        },
+        {
+            "name": "animal_remains3"
         }
     ]
-}
+}

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

@@ -1,3 +1,31 @@
 # 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.

二进制
Wiki/src/images/doctors_handbook.png