diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs index 268fadcd85f..b8d92546a70 100644 --- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs +++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerControl.xaml.cs @@ -190,6 +190,7 @@ private static string GetStatus(MobState mobState) return mobState switch { MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"), + MobState.SoftCritical => Loc.GetString("health-analyzer-window-entity-soft-critical-text"), // ADT-Tweak 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"), diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 6a4a04b0fbb..7176bb3e614 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -140,7 +140,9 @@ protected override void Draw(in OverlayDrawArgs args) var totalDamage = _damageable.GetTotalDamage((uid, dmg)); if (_mobStateSystem.IsAlive(uid, component)) { - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) && + // ADT-Tweak + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.SoftCritical, out var threshold, thresholds) && + !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out threshold, thresholds) && !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds)) return (1, false); @@ -150,8 +152,10 @@ protected override void Draw(in OverlayDrawArgs args) if (_mobStateSystem.IsCritical(uid, component)) { - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, thresholds) || - !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out var deadThreshold, thresholds)) + // ADT-Tweak + if ((!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.SoftCritical, out var critThreshold, thresholds) + && !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out critThreshold, thresholds)) + || !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out var deadThreshold, thresholds)) { return (1, true); } diff --git a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs index cf824833efb..1d97a1ecdfd 100644 --- a/Content.Client/RoundEnd/RoundEndSummaryUIController.cs +++ b/Content.Client/RoundEnd/RoundEndSummaryUIController.cs @@ -40,7 +40,8 @@ public void OpenRoundEndSummaryWindow(RoundEndMessageEvent message) return; _window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, - message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, EntityManager); + message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, EntityManager, + message.RoundReport, message.SpeciesCensus); } public void OnSystemLoaded(ClientGameTicker system) diff --git a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs index 402c7340564..761f5bfd7cc 100644 --- a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs +++ b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs @@ -8,21 +8,28 @@ using static Robust.Client.UserInterface.Controls.BoxContainer; // Goob Station - End of Round Screen using Content.Client.Stylesheets; +using Content.Shared.ADT.RoundEnd; // ADT-Tweak using Content.Shared.Mobs; namespace Content.Client.RoundEnd { public sealed class RoundEndSummaryWindow : DefaultWindow { - private readonly IEntityManager _entityManager; - public int RoundId; + private readonly IEntityManager _entityManager; + private readonly List _roundReport; + private readonly Dictionary _speciesCensus; + public int RoundId; public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, int roundId, - RoundEndMessageEvent.RoundEndPlayerInfo[] info, IEntityManager entityManager) + RoundEndMessageEvent.RoundEndPlayerInfo[] info, IEntityManager entityManager, + List? roundReport = null, + Dictionary? speciesCensus = null) { _entityManager = entityManager; + _roundReport = roundReport ?? new List(); + _speciesCensus = speciesCensus ?? new Dictionary(); - MinSize = SetSize = new Vector2(520, 580); + MinSize = SetSize = new Vector2(560, 620); Title = Loc.GetString("round-end-summary-window-title"); @@ -36,6 +43,8 @@ public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, var roundEndTabs = new TabContainer(); roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId)); roundEndTabs.AddChild(MakePlayerManifestTab(info)); + roundEndTabs.AddChild(MakeCrewTableTab(info)); // ADT-Tweak + roundEndTabs.AddChild(MakeStatsTab(gm, roundTimeSpan, roundId)); // ADT-Tweak ContentsContainer.AddChild(roundEndTabs); @@ -92,7 +101,7 @@ private BoxContainer MakeRoundEndSummaryTab(string gamemode, string roundEnd, Ti return roundEndSummaryTab; } - //ADT-tweak-start + // ADT-Tweak-start //всё в этом регионе сильно модифицировано [Obsolete("This is only used for the end of round summary, and is not intended to be used for anything else. It will be removed once we have a better way to track this information.")] private BoxContainer MakePlayerManifestTab(RoundEndMessageEvent.RoundEndPlayerInfo[] playersInfo) @@ -328,11 +337,248 @@ private BoxContainer MakePlayerManifestTab(RoundEndMessageEvent.RoundEndPlayerIn playerInfoContainer.AddChild(panel); } - playerInfoContainerScrollbox.AddChild(playerInfoContainer); - playerManifestTab.AddChild(playerInfoContainerScrollbox); + playerInfoContainerScrollbox.AddChild(playerInfoContainer); + playerManifestTab.AddChild(playerInfoContainerScrollbox); - return playerManifestTab; + return playerManifestTab; + } + + // ADT-Tweak-start + private BoxContainer MakeStatsTab(string gamemode, TimeSpan roundDuration, int roundId) + { + var statsTab = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Name = Loc.GetString("round-end-report-tab-title") + }; + + var scroll = new ScrollContainer + { + VerticalExpand = true, + Margin = new Thickness(10) + }; + + var container = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + SeparationOverride = 2 + }; + + AddReportLine(container, Loc.GetString("round-end-report-round-id", ("roundId", roundId))); + AddReportLine(container, Loc.GetString("round-end-report-gamemode", ("gamemode", gamemode))); + AddReportLine(container, Loc.GetString("round-end-report-duration", + ("hours", roundDuration.Hours), + ("minutes", roundDuration.Minutes), + ("seconds", roundDuration.Seconds))); + + AddReportCategory(container, RoundEndStatCategory.Summary, "round-end-report-category-summary"); + AddReportCategory(container, RoundEndStatCategory.FirstDeath, "round-end-report-category-first-death"); + AddReportCategory(container, RoundEndStatCategory.Economy, "round-end-report-category-economy"); + AddReportCategory(container, RoundEndStatCategory.Misc, "round-end-report-category-misc"); + AddSpeciesCensus(container); + + scroll.AddChild(container); + statsTab.AddChild(scroll); + + return statsTab; + } + + private void AddReportCategory(BoxContainer container, RoundEndStatCategory category, string headerLocId) + { + var entries = _roundReport + .Where(e => e.Category == category) + .OrderBy(e => e.Order) + .ToArray(); + + if (entries.Length == 0) + return; + + AddCategoryHeader(container, headerLocId); + + foreach (var entry in entries) + { + AddReportLine(container, FormatReportEntry(entry), 8); + } + } + + private void AddReportLine(BoxContainer container, string markup, int indent = 0) + { + var label = new RichTextLabel { Margin = new Thickness(indent, 0, 0, 0) }; + label.SetMarkup(markup); + container.AddChild(label); + } + + private void AddCategoryHeader(BoxContainer container, string headerLocId) + { + var label = new Label + { + Text = Loc.GetString(headerLocId), + StyleClasses = { StyleNano.StyleClassLabelHeading }, + Margin = new Thickness(0, 8, 0, 2) + }; + container.AddChild(label); + } + + /// + /// Resolves a report line, translating any locale-id arguments client-side. + /// + private static string FormatReportEntry(RoundEndStatEntry entry) + { + var args = new List<(string, object)>(); + + foreach (var (key, value) in entry.Args) + args.Add((key, value)); + + foreach (var (key, locId) in entry.LocArgs) + args.Add((key, Loc.GetString(locId))); + + return Loc.GetString(entry.LocId, args.ToArray()); + } + + private void AddSpeciesCensus(BoxContainer container) + { + if (_speciesCensus.Count == 0) + return; + + AddCategoryHeader(container, "round-end-report-category-census"); + + AddReportLine(container, Loc.GetString("round-end-report-species-header", + ("count", _speciesCensus.Count)), 8); + + foreach (var (species, count) in _speciesCensus.OrderByDescending(p => p.Value)) + { + var name = Loc.TryGetString($"species-name-{species.ToLowerInvariant()}", out var localized) + ? localized + : species; + + AddReportLine(container, Loc.GetString("round-end-report-species-line", + ("species", name), ("count", count)), 16); } } - //ADT-tweak-end + // ADT-Tweak-end + + // ADT-Tweak-start + private BoxContainer MakeCrewTableTab(RoundEndMessageEvent.RoundEndPlayerInfo[] playersInfo) + { + var crewTab = new BoxContainer + { + Orientation = LayoutOrientation.Vertical, + Name = Loc.GetString("round-end-summary-window-crew-tab-title") + }; + + var scroll = new ScrollContainer + { + VerticalExpand = true, + Margin = new Thickness(10) + }; + + var container = new BoxContainer + { + Orientation = LayoutOrientation.Vertical + }; + + var crew = playersInfo.Where(p => !p.Observer).ToArray(); + var observers = playersInfo.Where(p => p.Observer).ToArray(); + + var alive = crew.Count(p => p.EntMobState != MobState.Dead && p.EntMobState != MobState.Invalid); + var dead = crew.Count(p => p.EntMobState == MobState.Dead); + var escaped = crew.Count(p => p.Escaped && p.EntMobState != MobState.Dead); + + var summaryLabel = new RichTextLabel { Margin = new Thickness(0, 0, 0, 8) }; + summaryLabel.SetMarkup(Loc.GetString("round-end-summary-window-crew-summary", + ("alive", alive), ("dead", dead), ("escaped", escaped), ("total", crew.Length))); + container.AddChild(summaryLabel); + + var grid = new GridContainer + { + Columns = 3, + HorizontalExpand = true + }; + + void AddHeader(string text) + { + var label = new Label + { + Text = text, + StyleClasses = { StyleNano.StyleClassLabelHeading }, + Margin = new Thickness(4, 2) + }; + grid.AddChild(label); + } + + AddHeader(Loc.GetString("round-end-summary-window-crew-name-header")); + AddHeader(Loc.GetString("round-end-summary-window-crew-role-header")); + AddHeader(Loc.GetString("round-end-summary-window-crew-status-header")); + + var sorted = crew + .OrderBy(p => p.EntMobState == MobState.Dead) + .ThenBy(p => p.PlayerICName ?? p.PlayerOOCName) + .Concat(observers.OrderBy(p => p.PlayerICName ?? p.PlayerOOCName)); + + foreach (var player in sorted) + { + var name = player.PlayerICName ?? player.PlayerOOCName; + + var nameLabel = new Label + { + Text = player.Antag ? $"{name} [?]" : name, + FontColorOverride = player.Antag ? Color.Red : (player.Observer ? Color.Gray : Color.White), + Margin = new Thickness(4, 1) + }; + grid.AddChild(nameLabel); + + var roleLabel = new Label + { + Text = Loc.GetString(player.Role), + FontColorOverride = player.Observer ? Color.Gray : Color.LightGray, + Margin = new Thickness(4, 1) + }; + grid.AddChild(roleLabel); + + string status; + Color statusColor; + if (player.Observer) + { + status = Loc.GetString("round-end-summary-window-crew-status-observer"); + statusColor = Color.Gray; + } + else if (player.EntMobState == MobState.Dead) + { + status = Loc.GetString("round-end-summary-window-crew-status-dead"); + statusColor = Color.Red; + } + else if (player.EntMobState == MobState.Invalid) + { + status = Loc.GetString("round-end-summary-window-crew-status-nobody"); + statusColor = Color.Gray; + } + else if (player.Escaped) + { + status = Loc.GetString("round-end-summary-window-crew-status-escaped"); + statusColor = Color.Green; + } + else + { + status = Loc.GetString("round-end-summary-window-crew-status-alive"); + statusColor = Color.Yellow; + } + + var statusLabel = new Label + { + Text = status, + FontColorOverride = statusColor, + Margin = new Thickness(4, 1) + }; + grid.AddChild(statusLabel); + } + + container.AddChild(grid); + scroll.AddChild(container); + crewTab.AddChild(scroll); + + return crewTab; + } + // ADT-Tweak-end + } + // ADT-Tweak-end } diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs index 0e08f8497e3..0700e5ca76d 100644 --- a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs +++ b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs @@ -131,6 +131,7 @@ private void UpdateOverlays(EntityUid entity, MobStateComponent? mobState, Damag _overlay.DeadLevel = 0; break; } + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: { if (!_mobThresholdSystem.TryGetDeadPercentage(entity, diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs b/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs index 1b9e4e2c4ab..03266084a57 100644 --- a/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs +++ b/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs @@ -171,7 +171,7 @@ protected override void Draw(in OverlayDrawArgs args) _oldPainLevel = PainLevel; } - level = State != MobState.Critical ? _oldOxygenLevel : 1f; + level = State is MobState.SoftCritical or MobState.Critical ? 1f : _oldOxygenLevel; // ADT-Tweak if (level > 0f) { diff --git a/Content.IntegrationTests/Tests/Medical/DefibrillatorTest.cs b/Content.IntegrationTests/Tests/Medical/DefibrillatorTest.cs index 18361a9d901..16998224076 100644 --- a/Content.IntegrationTests/Tests/Medical/DefibrillatorTest.cs +++ b/Content.IntegrationTests/Tests/Medical/DefibrillatorTest.cs @@ -51,7 +51,7 @@ public async Task KillAndReviveTest() }); // Get the damage needed to kill or crit the target. - var critThreshold = mobThresholdsSystem.GetThresholdForState(STarget.Value, MobState.Critical); + var critThreshold = mobThresholdsSystem.GetThresholdForState(STarget.Value, MobState.SoftCritical); // ADT-Tweak var deathThreshold = mobThresholdsSystem.GetThresholdForState(STarget.Value, MobState.Dead); var critDamage = new DamageSpecifier(ProtoMan.Index(BluntDamageTypeId), (critThreshold + deathThreshold) / 2); var deathDamage = new DamageSpecifier(ProtoMan.Index(BluntDamageTypeId), deathThreshold); @@ -96,7 +96,7 @@ public async Task KillAndReviveTest() await RunSeconds((float)cooldown.TotalSeconds); await Interact(); - // The target should be revived, but in crit. - Assert.That(targetMobState.CurrentState, Is.EqualTo(MobState.Critical), "Target mob was not revived from being defibrillated."); + // The target should be revived, but in soft crit. + Assert.That(targetMobState.CurrentState, Is.EqualTo(MobState.SoftCritical), "Target mob was not revived from being defibrillated."); // ADT-Tweak } } diff --git a/Content.Server/ADT/Economy/BankCardSystem.cs b/Content.Server/ADT/Economy/BankCardSystem.cs index bedc4a91168..2977c8ef654 100644 --- a/Content.Server/ADT/Economy/BankCardSystem.cs +++ b/Content.Server/ADT/Economy/BankCardSystem.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Access.Systems; using Content.Server.Cargo.Components; @@ -245,6 +245,8 @@ public bool TryGetAccount(int accountId, [NotNullWhen(true)] out BankAccount? ac return account != null; } + public IReadOnlyList GetAllAccounts() => _accounts; + public int GetBalance(int accountId) { if (!TryGetAccount(accountId, out var account)) diff --git a/Content.Server/ADT/Morph/MorphSystem.cs b/Content.Server/ADT/Morph/MorphSystem.cs index 0c20ebcc3e6..3749bf1c844 100644 --- a/Content.Server/ADT/Morph/MorphSystem.cs +++ b/Content.Server/ADT/Morph/MorphSystem.cs @@ -320,6 +320,7 @@ private void OnDevourAction(EntityUid uid, MorphComponent component, MorphDevour { switch (targetState.CurrentState) { + case MobState.SoftCritical: case MobState.Critical: _popupSystem.PopupClient(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid, uid); break; diff --git a/Content.Server/ADT/RoundEnd/RoundEndStatsSystem.cs b/Content.Server/ADT/RoundEnd/RoundEndStatsSystem.cs new file mode 100644 index 00000000000..5947a205201 --- /dev/null +++ b/Content.Server/ADT/RoundEnd/RoundEndStatsSystem.cs @@ -0,0 +1,380 @@ +using System.Linq; +using Content.Server.ADT.Economy; +using Content.Server.GameTicking; +using Content.Server.Shuttles.Systems; +using Content.Shared.ADT.LastWords; +using Content.Shared.ADT.Mining; +using Content.Shared.ADT.RoundEnd; +using Content.Shared.Body; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.GameTicking; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Nutrition; +using Content.Shared.Players; +using Content.Shared.Roles; +using Content.Shared.Roles.Components; +using Content.Shared.Slippery; +using Content.Shared.Station.Components; +using Robust.Shared.Player; + +namespace Content.Server.ADT.RoundEnd; + +public sealed class RoundEndStatsSystem : EntitySystem +{ + [Dependency] private readonly BankCardSystem _bankCard = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; + [Dependency] private readonly ISharedPlayerManager _playerManager = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly StationIntegritySystem _integrity = default!; + + private FirstDeathRecord? _firstDeath; + + private int _totalSlips; + private int _clownSlips; + private int _oreMined; + private int _clownsBeaten; + private int _bitesEaten; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundRestart); + SubscribeLocalEvent(OnStatsCollect); + + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnSlip); + SubscribeLocalEvent(OnOreMined); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnIngesting); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + _firstDeath = null; + _totalSlips = 0; + _clownSlips = 0; + _oreMined = 0; + _clownsBeaten = 0; + _bitesEaten = 0; + } + + private void OnMobStateChanged(MobStateChangedEvent ev) + { + if (_firstDeath != null || ev.NewMobState != MobState.Dead) + return; + + if (!TryComp(ev.Target, out var container) + || !_mind.TryGetMind(ev.Target, out var mindId, out var mind, container)) + return; + + var damage = 0; + if (TryComp(ev.Target, out var damageable)) + damage = (int) _damageable.GetTotalDamage((ev.Target, damageable)); + + _firstDeath = new FirstDeathRecord + { + Name = mind.CharacterName ?? Name(ev.Target), + JobLocId = GetJobLocId(mindId), + Damage = damage, + LastWords = CompOrNull(mindId)?.LastWords ?? string.Empty, + }; + } + + private void OnSlip(Entity ent, ref SlipEvent ev) + { + _totalSlips++; + + if (IsClown(ev.Slipped)) + _clownSlips++; + } + + private void OnOreMined(ref OreMinedEvent ev) + { + _oreMined += ev.Amount; + } + + private void OnDamageChanged(Entity ent, ref DamageChangedEvent args) + { + if (!args.DamageIncreased || args.DamageDelta is not { } delta || delta.GetTotal() < 1) + return; + + if (IsClown(ent.Owner)) + _clownsBeaten++; + } + + private void OnIngesting(Entity ent, ref IngestingEvent args) + { + _bitesEaten++; + } + + private void OnStatsCollect(ref RoundEndStatsCollectEvent ev) + { + CollectSummary(ev); + CollectFirstDeath(ev); + CollectEconomy(ev); + CollectMisc(ev); + CollectSpeciesCensus(ev); + } + + private void CollectSummary(RoundEndStatsCollectEvent ev) + { + var total = 0; + var survivors = 0; + var escapees = 0; + var shuttleEscapees = 0; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var mindId, out var mind)) + { + if (_roles.MindHasRole(mindId)) + continue; + + total++; + + if (GetMob(mind) is not { } mob || _mobState.IsDead(mob)) + continue; + + survivors++; + + if (!_emergencyShuttle.IsTargetEscaping(mob)) + continue; + + escapees++; + shuttleEscapees++; + } + + ev.Add(RoundEndStatCategory.Summary, "round-end-report-station-integrity") + .WithArg("value", _integrity.GetIntegrity()); + + ev.Add(RoundEndStatCategory.Summary, "round-end-report-population", 1) + .WithArg("value", total); + + if (_emergencyShuttle.EmergencyShuttleArrived) + { + ev.Add(RoundEndStatCategory.Summary, "round-end-report-evacuation-rate", 2) + .WithRate("value", escapees, total); + + ev.Add(RoundEndStatCategory.Summary, "round-end-report-shuttle-rate", 3) + .WithRate("value", shuttleEscapees, total); + } + + ev.Add(RoundEndStatCategory.Summary, "round-end-report-survival-rate", 4) + .WithRate("value", survivors, total); + } + + private void CollectFirstDeath(RoundEndStatsCollectEvent ev) + { + if (_firstDeath is not { } death) + { + ev.Add(RoundEndStatCategory.FirstDeath, "round-end-report-no-deaths"); + return; + } + + ev.Add(RoundEndStatCategory.FirstDeath, "round-end-report-first-death") + .WithArg("name", death.Name) + .WithArg("value", death.Damage) + .WithLocArg("job", death.JobLocId); + + if (death.LastWords.Length > 0) + { + ev.Add(RoundEndStatCategory.FirstDeath, "round-end-report-first-death-last-words", 1) + .WithArg("lastWords", death.LastWords); + } + } + + private void CollectEconomy(RoundEndStatsCollectEvent ev) + { + var vault = 0; + var crew = 0; + var richest = 0; + Entity? richestMind = null; + var richestName = string.Empty; + + foreach (var account in _bankCard.GetAllAccounts()) + { + if (account.CommandBudgetAccount) + continue; + + vault += account.Balance; + crew++; + + if (account.Balance <= richest) + continue; + + richest = account.Balance; + richestMind = account.Mind; + richestName = account.Name; + } + + ev.Add(RoundEndStatCategory.Economy, "round-end-report-station-vault") + .WithArg("value", vault); + + if (crew > 0) + { + ev.Add(RoundEndStatCategory.Economy, "round-end-report-average-wealth", 1) + .WithArg("value", vault / crew); + } + + if (richestMind is { } mind) + { + DescribeCharacter( + ev.Add(RoundEndStatCategory.Economy, "round-end-report-richest", 2) + .WithArg("value", richest), + mind, + richestName); + } + else + { + ev.Add(RoundEndStatCategory.Economy, "round-end-report-nobody-rich", 2); + } + } + + private void CollectMisc(RoundEndStatsCollectEvent ev) + { + ev.Add(RoundEndStatCategory.Misc, "round-end-report-ore-mined") + .WithArg("value", _oreMined); + + ev.Add(RoundEndStatCategory.Misc, "round-end-report-food-eaten", 1) + .WithArg("value", _bitesEaten); + + ev.Add(RoundEndStatCategory.Misc, "round-end-report-slips", 2) + .WithArg("value", _totalSlips) + .WithArg("clown", _clownSlips); + + ev.Add(RoundEndStatCategory.Misc, "round-end-report-clowns-beaten", 3) + .WithArg("value", _clownsBeaten); + + ev.Add(RoundEndStatCategory.Misc, "round-end-report-corpses", 4) + .WithArg("value", CountCorpses()); + + var worstDamage = 0; + Entity? worstMind = null; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var mindId, out var mind)) + { + if (GetMob(mind) is not { } mob || _mobState.IsDead(mob)) + continue; + + if (!TryComp(mob, out var damageable)) + continue; + + var total = (int) _damageable.GetTotalDamage((mob, damageable)); + if (total <= worstDamage) + continue; + + worstDamage = total; + worstMind = (mindId, mind); + } + + if (worstMind is { } worst) + { + DescribeCharacter( + ev.Add(RoundEndStatCategory.Misc, "round-end-report-battered-survivor", 5) + .WithArg("value", worstDamage), + worst); + } + } + + private void CollectSpeciesCensus(RoundEndStatsCollectEvent ev) + { + var census = new Dictionary(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out var mind)) + { + if (mind.CurrentEntity is not { } mob) + continue; + + if (!TryComp(mob, out var profile)) + continue; + + var species = profile.Species.Id; + census[species] = census.GetValueOrDefault(species) + 1; + } + + ev.SpeciesCensus = census; + } + + private void DescribeCharacter(RoundEndStatEntry entry, Entity mind, string? nameOverride = null) + { + var name = string.IsNullOrEmpty(nameOverride) ? mind.Comp.CharacterName : nameOverride; + entry.WithArg("name", name ?? Loc.GetString("round-end-report-unknown-name")); + entry.WithLocArg("job", GetJobLocId(mind.Owner)); + + var userId = mind.Comp.UserId ?? mind.Comp.OriginalOwnerUserId; + if (userId != null && _playerManager.TryGetPlayerData(userId.Value, out var data)) + entry.WithArg("player", data.ContentData()?.Name ?? data.UserName); + else + entry.WithArg("player", Loc.GetString("round-end-report-unknown-name")); + } + + private string GetJobLocId(EntityUid mindId) + { + var job = _roles.MindGetAllRoleInfo(mindId).FirstOrDefault(role => !role.Antagonist); + return job.Name ?? "game-ticker-unknown-role"; + } + + private EntityUid? GetMob(MindComponent mind) + { + var mob = mind.CurrentEntity ?? mind.LastMob; + if (mob is null && mind.OriginalOwnedEntity is { } netEnt) + mob = GetEntity(netEnt); + + if (mob is not { } uid || TerminatingOrDeleted(uid) || !HasComp(uid)) + return null; + + return uid; + } + + private int CountCorpses() + { + var corpses = 0; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out _, out var mobState, out _, out var xform)) + { + if (mobState.CurrentState != MobState.Dead) + continue; + + if (xform.GridUid is not { } grid || !HasComp(grid)) + continue; + + corpses++; + } + + return corpses; + } + + private bool IsClown(EntityUid uid) + { + if (!TryComp(uid, out var mindContainer) + || !_mind.TryGetMind(uid, out var mindId, out _, mindContainer)) + return false; + + foreach (var role in _roles.MindGetAllRoleInfo(mindId)) + { + if (!role.Antagonist && role.Prototype == "Clown") + return true; + } + + return false; + } + + private struct FirstDeathRecord + { + public string Name; + public string JobLocId; + public int Damage; + public string LastWords; + } +} diff --git a/Content.Server/ADT/RoundEnd/StationIntegritySystem.cs b/Content.Server/ADT/RoundEnd/StationIntegritySystem.cs new file mode 100644 index 00000000000..517f7ca94db --- /dev/null +++ b/Content.Server/ADT/RoundEnd/StationIntegritySystem.cs @@ -0,0 +1,99 @@ +using Content.Server.Construction.Components; +using Content.Shared.Doors.Components; +using Content.Shared.GameTicking; +using Content.Shared.Station.Components; +using Content.Shared.Tag; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.ADT.RoundEnd; + +public sealed class StationIntegritySystem : EntitySystem +{ + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly TagSystem _tags = default!; + + private static readonly ProtoId WallTag = "Wall"; + private static readonly ProtoId WindowTag = "Window"; + + private StationState? _startState; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundStarted); + SubscribeLocalEvent(OnRoundRestart); + } + + private void OnRoundStarted(RoundStartedEvent ev) + { + _startState = Count(); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + _startState = null; + } + + public int GetIntegrity() + { + if (_startState is not { } start) + return 100; + + return Math.Clamp((int) MathF.Round(start.Score(Count()) * 100f), 0, 100); + } + + private StationState Count() + { + var state = new StationState(); + + var grids = EntityQueryEnumerator(); + while (grids.MoveNext(out var gridUid, out _, out var grid)) + { + var tiles = _map.GetAllTilesEnumerator(gridUid, grid); + while (tiles.MoveNext(out _)) + { + state.Floor++; + } + } + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var xform)) + { + if (xform.GridUid is not { } gridUid2 || !HasComp(gridUid2)) + continue; + + if (HasComp(uid)) + state.Door++; + else if (_tags.HasTag(uid, WallTag)) + state.Wall++; + else if (_tags.HasTag(uid, WindowTag)) + state.Window++; + else if (HasComp(uid)) + state.Machine++; + } + + return state; + } + + private sealed class StationState + { + public int Floor; + public int Wall; + public int Window; + public int Door; + public int Machine; + + public float Score(StationState result) + { + var output = 0f; + output += result.Floor / (float) Math.Max(Floor, 1); + output += result.Wall / (float) Math.Max(Wall, 1); + output += result.Window / (float) Math.Max(Window, 1); + output += result.Door / (float) Math.Max(Door, 1); + output += result.Machine / (float) Math.Max(Machine, 1); + return output / 5f; + } + } +} diff --git a/Content.Server/ADT/Shadowling/ADTShadowlingAbilitySystem.Ascension.cs b/Content.Server/ADT/Shadowling/ADTShadowlingAbilitySystem.Ascension.cs index a181810f40f..ec227241c25 100644 --- a/Content.Server/ADT/Shadowling/ADTShadowlingAbilitySystem.Ascension.cs +++ b/Content.Server/ADT/Shadowling/ADTShadowlingAbilitySystem.Ascension.cs @@ -5,6 +5,7 @@ using Content.Shared.Light.Components; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Robust.Shared.Audio; using Robust.Shared.Player; namespace Content.Server.ADT.Shadowling; @@ -84,7 +85,7 @@ private void OnAscendDoAfter(Entity ent, ref ADTShadowli private void FinishAscend(Entity ent, ADTShadowlingAscendActionComponent ascend) { - _audio.PlayGlobal(ascend.Sound, Filter.Broadcast(), true); + _audio.PlayGlobal(ascend.Sound, Filter.Broadcast(), true, new AudioParams { Volume = -8f }); foreach (var nearby in _lookup.GetEntitiesInRange(ent.Owner, ascend.ShockwaveRange)) { diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 4eafbf3d2e3..41bb56af1c2 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -223,6 +223,20 @@ public override void TrySendInGameICMessage( } // ADT-Port-End DeltaV - End hushed trait logic + // ADT-Tweak-start + if (desiredType == InGameICChatType.Speak && _mobStateSystem.IsSoftCritical(source)) + desiredType = InGameICChatType.Whisper; + // ADT-Tweak-end + + // ADT-Tweak + if (_mobStateSystem.IsSoftCritical(source)) + { + checkRadioPrefix = false; + + if (TryProcessRadioMessage(source, message, out var stripped, out _, true)) + message = stripped; + } + // ADT Languages start bool shouldCapitalize = (desiredType != InGameICChatType.Emote); @@ -237,7 +251,7 @@ public override void TrySendInGameICMessage( // ADT Alternative speech start var altEv = new AlternativeSpeechEvent(sanitizedMessage, false, desiredType); - if (TryProcessRadioMessage(source, sanitizedMessage, out var altSpeechRadioResult, out _, true)) + if (checkRadioPrefix && TryProcessRadioMessage(source, sanitizedMessage, out var altSpeechRadioResult, out _, true)) { altEv.Radio = true; altEv.Message = altSpeechRadioResult; diff --git a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs index 5597c47fdb0..3be3ff0c565 100644 --- a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs +++ b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs @@ -122,7 +122,7 @@ private void OnDamageChanged(EntityUid uid, DamageForceSayComponent component, D private void OnMobStateChanged(EntityUid uid, DamageForceSayComponent component, MobStateChangedEvent args) { - if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.Critical or MobState.Dead }) + if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.SoftCritical or MobState.Critical or MobState.Dead }) // ADT-Tweak return; // no suffix for the drama diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index b5b13fd4aa3..f0cfd014b36 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -5,6 +5,7 @@ using Content.Server.GameTicking.Events; using Content.Server.Maps; using Content.Server.Roles; +using Content.Server.Shuttles.Systems; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; @@ -47,6 +48,7 @@ public sealed partial class GameTicker [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly ITaskManager _taskManager = default!; [Dependency] private readonly IVoteManager _voteManager = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( "ss14_round_number", @@ -631,6 +633,20 @@ public void ShowRoundEndScoreboard(string text = "") // ADT-tweak-end + // ADT-Tweak-start + var escaped = false; + EntityUid? statusMob = lastMob; + if (statusMob is null && mind.OriginalOwnedEntity is not null) + statusMob = GetEntity(mind.OriginalOwnedEntity.Value); + + if (statusMob.HasValue + && mobState != MobState.Dead + && !TerminatingOrDeleted(statusMob.Value)) + { + escaped = _emergencyShuttle.IsTargetEscaping(statusMob.Value); + } + // ADT-Tweak-end + var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo() { // Note that contentPlayerData?.Name sticks around after the player is disconnected. @@ -651,7 +667,8 @@ public void ShowRoundEndScoreboard(string text = "") // ADT-tweak-start: manifest LastWords = lastWords, EntMobState = mobState, - DamagePerGroup = damagePerGroup + DamagePerGroup = damagePerGroup, + Escaped = escaped // ADT-tweak-end }; listOfPlayerInfo.Add(playerEndRoundInfo); @@ -670,6 +687,14 @@ public void ShowRoundEndScoreboard(string text = "") listOfPlayerInfoFinal, sound ); + + // ADT-Tweak-start + var statsEv = new Content.Shared.ADT.RoundEnd.RoundEndStatsCollectEvent(); + RaiseLocalEvent(ref statsEv); + roundEndMessageEvent.RoundReport = statsEv.Entries; + roundEndMessageEvent.SpeciesCensus = statsEv.SpeciesCensus; + // ADT-Tweak-end + RaiseNetworkEvent(roundEndMessageEvent); RaiseLocalEvent(roundEndMessageEvent); RaiseLocalEvent(new RoundEndedEvent(RoundId, roundDuration)); // Corvax diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 87df7a61fbf..f7d0a7fcec3 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -108,6 +108,7 @@ private void OnMobStateChanged(Entity component RegisterGhostRole((component, ghostRole)); break; } + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: case MobState.Dead: UnregisterGhostRole((component, ghostRole)); diff --git a/Content.Server/Mining/MiningSystem.cs b/Content.Server/Mining/MiningSystem.cs index 6b1b0fed4d8..c0305775695 100644 --- a/Content.Server/Mining/MiningSystem.cs +++ b/Content.Server/Mining/MiningSystem.cs @@ -40,6 +40,14 @@ private void OnDestruction(EntityUid uid, OreVeinComponent component, Destructio { Spawn(proto.OreEntity, coords.Offset(_random.NextVector2(0.2f))); } + + // ADT-Tweak-start + if (toSpawn > 0) + { + var oreEv = new Content.Shared.ADT.Mining.OreMinedEvent(toSpawn); + RaiseLocalEvent(ref oreEv); + } + // ADT-Tweak-end } private void OnMapInit(EntityUid uid, OreVeinComponent component, MapInitEvent args) diff --git a/Content.Server/NPC/Systems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs index 5788d20ad8c..1ddaa0d58e0 100644 --- a/Content.Server/NPC/Systems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -166,6 +166,7 @@ public void OnMobStateChange(EntityUid uid, HTNComponent component, MobStateChan case MobState.Alive: WakeNPC(uid, component); break; + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: case MobState.Dead: SleepNPC(uid, component); diff --git a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs index f6de79fee54..9852b2f2ccd 100644 --- a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs +++ b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs @@ -72,6 +72,7 @@ private void UpdateEssenceAmount(EntityUid uid, EssenceComponent component) else component.EssenceAmount = _random.NextFloat(45f, 70f); break; + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: component.EssenceAmount = _random.NextFloat(35f, 50f); break; diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 500ed0a3cbf..deb1287c6c3 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -489,5 +489,10 @@ public enum LogType /// A player grabbed another player /// Grab = 105, + + /// + /// A player in soft crit tried to catch their breath. + /// + CatchBreath = 106, // ADT End } diff --git a/Content.Shared/ADT/Mining/OreMinedEvent.cs b/Content.Shared/ADT/Mining/OreMinedEvent.cs new file mode 100644 index 00000000000..9ec98af54f4 --- /dev/null +++ b/Content.Shared/ADT/Mining/OreMinedEvent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.ADT.Mining; + +/// +/// Raised undirected whenever ore entities spawn from a mined vein. +/// +[ByRefEvent] +public readonly record struct OreMinedEvent(int Amount); diff --git a/Content.Shared/ADT/Mobs/TryCatchBreathAlertEvent.cs b/Content.Shared/ADT/Mobs/TryCatchBreathAlertEvent.cs new file mode 100644 index 00000000000..34bff2244fa --- /dev/null +++ b/Content.Shared/ADT/Mobs/TryCatchBreathAlertEvent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Alert; +using Content.Shared.DoAfter; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.Mobs; + +public sealed partial class TryCatchBreathAlertEvent : BaseAlertEvent +{ + public TryCatchBreathAlertEvent(EntityUid user, ProtoId alertId) + : base(user, alertId) + { + } +} + +[Serializable, NetSerializable] +public sealed partial class TryCatchBreathDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/ADT/Mobs/TryCatchBreathSystem.cs b/Content.Shared/ADT/Mobs/TryCatchBreathSystem.cs new file mode 100644 index 00000000000..ef91d97988e --- /dev/null +++ b/Content.Shared/ADT/Mobs/TryCatchBreathSystem.cs @@ -0,0 +1,118 @@ +using Content.Shared.ADT.Mobs; +using Content.Shared.Administration.Logs; +using Content.Shared.Damage; +using Content.Shared.Damage.Systems; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Popups; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Random; + +namespace Content.Shared.ADT.Mobs; + +public sealed class TryCatchBreathSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damage = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + private const float DoAfterTime = 6f; + + private const string AudioPath = "/Audio/ADT/Alerts/CatchBreath/"; + + public override void Initialize() + { + SubscribeLocalEvent(OnAlertClicked); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnAlertClicked(TryCatchBreathAlertEvent ev) + { + if (!_net.IsServer) + return; + + var uid = ev.User; + + if (CompOrNull(uid)?.CurrentState != MobState.SoftCritical) + return; + + var args = new DoAfterArgs(EntityManager, uid, DoAfterTime, new TryCatchBreathDoAfterEvent(), uid) + { + Broadcast = true, + BreakOnMove = true, + BreakOnDamage = true, + NeedHand = false, + RequireCanInteract = false, + CancelDuplicate = true, + BlockDuplicate = true, + }; + + _doAfter.TryStartDoAfter(args); + + _popup.PopupEntity(Loc.GetString("catch-breath-try"), uid); + _audio.PlayEntity(new SoundPathSpecifier(AudioPath + "catch-breath-try.ogg"), uid, uid); + + _adminLogger.Add(LogType.CatchBreath, LogImpact.Low, $"{ToPrettyString(uid):user} started trying to catch their breath"); + } + + private void OnDoAfter(TryCatchBreathDoAfterEvent ev) + { + if (!_net.IsServer || ev.Cancelled) + return; + + var uid = ev.User; + + if (CompOrNull(uid)?.CurrentState != MobState.SoftCritical) + return; + + var roll = _random.NextFloat(); + var damage = new DamageSpecifier(); + string popup; + string sound; + + if (roll < 0.03f) + { + damage.DamageDict.Add("Blunt", -2); + damage.DamageDict.Add("Slash", -2); + damage.DamageDict.Add("Piercing", -2); + damage.DamageDict.Add("Asphyxiation", -10); + popup = "catch-breath-blunt-success"; + sound = "catch-breath-bluntsuccess.ogg"; + } + else if (roll < 0.63f) + { + damage.DamageDict.Add("Asphyxiation", -7); + popup = "catch-breath-success"; + sound = "catch-breath-success.ogg"; + } + else if (roll < 0.78f) + { + damage.DamageDict.Add("Asphyxiation", 5); + popup = "catch-breath-failure"; + sound = "catch-breath-failure.ogg"; + } + else + { + popup = "catch-breath-nothing"; + sound = "catch-breath-nothing.ogg"; + } + + _adminLogger.Add(LogType.CatchBreath, LogImpact.Low, $"{ToPrettyString(uid):user} rolled {roll} trying to catch their breath: {popup}"); + + _popup.PopupEntity(Loc.GetString(popup), uid); + _audio.PlayEntity(new SoundPathSpecifier(AudioPath + sound), uid, uid); + + if (damage.DamageDict.Count > 0) + _damage.TryChangeDamage(uid, damage); + + ev.Repeat = false; + } +} diff --git a/Content.Shared/ADT/RoundEnd/RoundEndStats.cs b/Content.Shared/ADT/RoundEnd/RoundEndStats.cs new file mode 100644 index 00000000000..95704e19581 --- /dev/null +++ b/Content.Shared/ADT/RoundEnd/RoundEndStats.cs @@ -0,0 +1,67 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.ADT.RoundEnd; + +[Serializable, NetSerializable] +public enum RoundEndStatCategory : byte +{ + Summary, + FirstDeath, + Economy, + Misc, + Census, +} + +[Serializable, NetSerializable] +public sealed class RoundEndStatEntry +{ + public const string YesLocId = "round-end-report-yes"; + public const string NoLocId = "round-end-report-no"; + + public RoundEndStatCategory Category; + public string LocId = string.Empty; + public Dictionary Args = new(); + public Dictionary LocArgs = new(); + public int Order; + + public RoundEndStatEntry() + { + } + + public RoundEndStatEntry(RoundEndStatCategory category, string locId, int order = 0) + { + Category = category; + LocId = locId; + Order = order; + } + + public RoundEndStatEntry WithArg(string key, string value) + { + Args[key] = value; + return this; + } + + public RoundEndStatEntry WithArg(string key, int value) + { + Args[key] = value.ToString(); + return this; + } + + public RoundEndStatEntry WithRate(string key, int value, int total) + { + Args[key] = value.ToString(); + Args[key + "Percent"] = total <= 0 ? "0" : (value * 100 / total).ToString(); + return this; + } + + public RoundEndStatEntry WithLocArg(string key, string locId) + { + LocArgs[key] = locId; + return this; + } + + public RoundEndStatEntry WithBool(string key, bool value) + { + return WithLocArg(key, value ? YesLocId : NoLocId); + } +} diff --git a/Content.Shared/ADT/RoundEnd/RoundEndStatsCollectEvent.cs b/Content.Shared/ADT/RoundEnd/RoundEndStatsCollectEvent.cs new file mode 100644 index 00000000000..d7942fd2d4e --- /dev/null +++ b/Content.Shared/ADT/RoundEnd/RoundEndStatsCollectEvent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.ADT.RoundEnd; + +[ByRefEvent] +public sealed class RoundEndStatsCollectEvent +{ + public List Entries = new(); + public Dictionary SpeciesCensus = new(); + + public RoundEndStatEntry Add(RoundEndStatCategory category, string locId, int order = 0) + { + var entry = new RoundEndStatEntry(category, locId, order); + Entries.Add(entry); + return entry; + } +} diff --git a/Content.Shared/Devour/DevourSystem.cs b/Content.Shared/Devour/DevourSystem.cs index 26313765b06..223b0574296 100644 --- a/Content.Shared/Devour/DevourSystem.cs +++ b/Content.Shared/Devour/DevourSystem.cs @@ -69,6 +69,7 @@ private void OnDevourAction(Entity ent, ref DevourActionEvent { switch (targetState.CurrentState) { + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: case MobState.Dead: diff --git a/Content.Shared/GameTicking/SharedGameTicker.cs b/Content.Shared/GameTicking/SharedGameTicker.cs index 460879fe5b1..8acaf3f0de7 100644 --- a/Content.Shared/GameTicking/SharedGameTicker.cs +++ b/Content.Shared/GameTicking/SharedGameTicker.cs @@ -1,4 +1,4 @@ -using Content.Shared.Roles; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Replays; @@ -195,6 +195,8 @@ public partial struct RoundEndPlayerInfo public bool Connected; + public bool Escaped; + //ADT-tweak-start public string? LastWords; @@ -217,6 +219,11 @@ public partial struct RoundEndPlayerInfo /// public ResolvedSoundSpecifier? RestartSound; + // ADT-Tweak-start + public List RoundReport = new(); + public Dictionary SpeciesCensus = new(); + // ADT-Tweak-end + public RoundEndMessageEvent( string gamemodeTitle, string roundEndText, diff --git a/Content.Shared/Medical/Healing/HealingSystem.cs b/Content.Shared/Medical/Healing/HealingSystem.cs index a7b84555325..008674b0360 100644 --- a/Content.Shared/Medical/Healing/HealingSystem.cs +++ b/Content.Shared/Medical/Healing/HealingSystem.cs @@ -280,7 +280,9 @@ public float GetScaledHealingPenalty(Entity ent, EntityUid target, EntityUid _mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var threshold, targetThresholds) && _damageable.GetTotalDamage(target) < threshold) { - _mobState.ChangeMobState(target, MobState.Critical, targetMobState, user); + _mobState.ChangeMobState(target, MobState.SoftCritical, targetMobState, user); // ADT-Tweak failedRevive = false; } diff --git a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs index df552a373a8..fd9b7eef89b 100644 --- a/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs +++ b/Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs @@ -506,7 +506,9 @@ public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots if (TryComp(sensor.User.Value, out var damageable)) status.TotalDamage = _damageableSystem.GetTotalDamage((sensor.User.Value, damageable)).Int(); - if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold)) + // ADT-Tweak + if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.SoftCritical, out var critThreshold) + || _mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out critThreshold)) status.TotalDamageThreshold = critThreshold.Value.Int(); if (sensor.Mode != SuitSensorMode.SensorCords) diff --git a/Content.Shared/Mobs/Components/MobStateComponent.cs b/Content.Shared/Mobs/Components/MobStateComponent.cs index 80a0674c96c..300229c7f2b 100644 --- a/Content.Shared/Mobs/Components/MobStateComponent.cs +++ b/Content.Shared/Mobs/Components/MobStateComponent.cs @@ -25,6 +25,7 @@ public sealed partial class MobStateComponent : Component public HashSet AllowedStates = new() { MobState.Alive, + MobState.SoftCritical, // ADT-Tweak MobState.Critical, MobState.Dead }; diff --git a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs index 0e37cf9b10e..1155c8e6223 100644 --- a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs +++ b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs @@ -28,6 +28,7 @@ public sealed partial class MobThresholdsComponent : Component public Dictionary> StateAlertDict = new() { {MobState.Alive, "HumanHealth"}, + {MobState.SoftCritical, "ADTHumanSoftCrit"}, // ADT-Tweak {MobState.Critical, "HumanCrit"}, {MobState.Dead, "HumanDead"}, }; diff --git a/Content.Shared/Mobs/MobState.cs b/Content.Shared/Mobs/MobState.cs index 1846232f4e3..f928ac7f0cc 100644 --- a/Content.Shared/Mobs/MobState.cs +++ b/Content.Shared/Mobs/MobState.cs @@ -15,8 +15,17 @@ public enum MobState : byte { Invalid = 0, Alive = 1, - Critical = 2, - Dead = 3 + + // ADT-Tweak-start + + /// + /// Barely conscious: the mob is prone, crawls, can only whisper and cannot act. + /// + SoftCritical = 2, + Critical = 3, + Dead = 4 + + // ADT-Tweak-end } /// diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.SoftCrit.cs b/Content.Shared/Mobs/Systems/MobStateSystem.SoftCrit.cs new file mode 100644 index 00000000000..5bb6a87a76f --- /dev/null +++ b/Content.Shared/Mobs/Systems/MobStateSystem.SoftCrit.cs @@ -0,0 +1,87 @@ +using Content.Shared.Damage.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; + +namespace Content.Shared.Mobs.Systems; + +// ADT-Tweak-start +public partial class MobStateSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + + private readonly Dictionary _stateAudio = new(); + + private const float SoftCritImmobileDamage = 150f; + private const float SoftCritSpeedModifier = 0.35f; + + private static readonly Dictionary StateAudio = new() + { + { MobState.SoftCritical, ("/Audio/ADT/Effects/soft_critical.ogg", true, -6f) }, + { MobState.Critical, ("/Audio/ADT/Effects/critical.ogg", true, -8f) }, + { MobState.Alive, ("/Audio/ADT/Effects/backtolife.ogg", false, -4f) }, + }; + + private void OnUpdateCanMove(EntityUid uid, MobStateComponent component, ref UpdateCanMoveEvent args) + { + if (component.CurrentState == MobState.SoftCritical) + { + if (TryComp(uid, out var damage) + && _damageable.GetTotalDamage((uid, damage)) > SoftCritImmobileDamage) + { + args.Cancel(); + } + + return; + } + + CheckAct(uid, component, args); + } + + private void OnSoftCritSpeed(EntityUid uid, MobStateComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (component.CurrentState != MobState.SoftCritical) + return; + + args.ModifySpeed(SoftCritSpeedModifier, SoftCritSpeedModifier); + } + + private void PlayStateAudio(EntityUid uid, MobState state) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (!StateAudio.TryGetValue(state, out var data)) + return; + + if (!TryComp(uid, out var actor)) + return; + + StopStateAudio(uid); + + var audio = _audio.PlayEntity( + new SoundPathSpecifier(data.Sound), + Filter.SinglePlayer(actor.PlayerSession), + uid, + false, + new AudioParams { Loop = data.Loop, Volume = data.Volume }); + + if (audio == null) + return; + + _stateAudio[uid] = audio.Value.Entity; + } + + private void StopStateAudio(EntityUid uid) + { + if (!_stateAudio.Remove(uid, out var audio)) + return; + + if (Exists(audio)) + QueueDel(audio); + } +} +// ADT-Tweak-end diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs index e29110e0b1f..bf37d0923b0 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs @@ -68,6 +68,11 @@ public void ChangeMobState(EntityUid entity, MobState mobState, MobStateComponen /// The new MobState protected virtual void OnEnterState(EntityUid entity, MobStateComponent component, MobState state) { + // ADT-Tweak-start + StopStateAudio(entity); + PlayStateAudio(entity, state); + // ADT-Tweak-end + OnStateEnteredSubscribers(entity, component, state); } @@ -91,6 +96,8 @@ protected virtual void OnStateChanged(EntityUid entity, MobStateComponent compon /// The old MobState protected virtual void OnExitState(EntityUid entity, MobStateComponent component, MobState state) { + StopStateAudio(entity); // ADT-Tweak + OnStateExitSubscribers(entity, component, state); } diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index db49374aef9..0655e150068 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -12,6 +12,7 @@ using Content.Shared.Item; using Content.Shared.Mobs.Components; using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; // ADT-Tweak using Content.Shared.Pointing; using Content.Shared.Pulling.Events; using Content.Shared.Speech; @@ -41,13 +42,14 @@ private void SubscribeEvents() SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); + SubscribeLocalEvent(OnUpdateCanMove); // ADT-Tweak SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnSleepAttempt); SubscribeLocalEvent(OnCombatModeShouldHandInteract); SubscribeLocalEvent(OnAttemptPacifiedAttack); SubscribeLocalEvent(OnDamageModify); + SubscribeLocalEvent(OnSoftCritSpeed); // ADT-Tweak SubscribeLocalEvent(OnUnbuckleAttempt); } @@ -72,6 +74,7 @@ private void CheckConcious(Entity ent, ref ConsciousAttemptEv switch (ent.Comp.CurrentState) { case MobState.Dead: + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: args.Cancelled = true; break; @@ -85,6 +88,9 @@ private void OnStateExitSubscribers(EntityUid target, MobStateComponent componen case MobState.Alive: //unused break; + case MobState.SoftCritical: // ADT-Tweak + _standing.Stand(target); + break; case MobState.Critical: _standing.Stand(target); break; @@ -102,7 +108,7 @@ private void OnStateExitSubscribers(EntityUid target, MobStateComponent componen // ADT-Tweak-start _moveMod.RefreshMovementSpeedModifiers(target); - if (state is MobState.Critical or MobState.Dead) + if (state is MobState.SoftCritical or MobState.Critical or MobState.Dead) // ADT-Tweak { _moveMod.RefreshMovementSpeedModifiers(target); } @@ -125,6 +131,14 @@ private void OnStateEnteredSubscribers(EntityUid target, MobStateComponent compo _appearance.SetData(target, MobStateVisuals.State, MobState.Alive); break; } + // ADT-Tweak-start + case MobState.SoftCritical: + { + Down(target); + _appearance.SetData(target, MobStateVisuals.State, MobState.SoftCritical); + break; + } + // ADT-Tweak-end case MobState.Critical: { Down(target); @@ -163,8 +177,12 @@ private void OnGettingStripped(EntityUid target, MobStateComponent component, Be // Incapacitated or dead targets get stripped two or three times as fast. Makes stripping corpses less tedious. if (IsDead(target, component)) args.Multiplier /= 3; - else if (IsCritical(target, component)) + // ADT-Tweak-start + else if (IsHardCritical(target, component)) + args.Multiplier /= 3; + else if (IsSoftCritical(target, component)) args.Multiplier /= 2; + // ADT-Tweak-end } private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAttemptEvent args) @@ -175,6 +193,10 @@ private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAtt return; } + // ADT-Tweak + if (component.CurrentState == MobState.SoftCritical) + return; + CheckAct(uid, component, args); } @@ -183,6 +205,7 @@ private void CheckAct(EntityUid target, MobStateComponent component, Cancellable switch (component.CurrentState) { case MobState.Dead: + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: args.Cancel(); break; diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.cs b/Content.Shared/Mobs/Systems/MobStateSystem.cs index 9a87131f43f..48d2891aa28 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.cs @@ -46,6 +46,19 @@ public bool IsAlive(EntityUid target, MobStateComponent? component = null) return component.CurrentState == MobState.Alive; } + /// + /// Check if a Mob is Soft Critical + /// + /// Target Entity + /// The MobState component owned by the target + /// If the entity is Soft Critical + public bool IsSoftCritical(EntityUid target, MobStateComponent? component = null) // ADT-Tweak + { + if (!_mobStateQuery.Resolve(target, ref component, false)) + return false; + return component.CurrentState == MobState.SoftCritical; + } + /// /// Check if a Mob is Critical /// @@ -53,6 +66,16 @@ public bool IsAlive(EntityUid target, MobStateComponent? component = null) /// The MobState component owned by the target /// If the entity is Critical public bool IsCritical(EntityUid target, MobStateComponent? component = null) + { + if (!_mobStateQuery.Resolve(target, ref component, false)) + return false; + return component.CurrentState is MobState.SoftCritical or MobState.Critical; // ADT-Tweak + } + + /// + /// Check if a Mob is Critical, ignoring soft crit. + /// + public bool IsHardCritical(EntityUid target, MobStateComponent? component = null) // ADT-Tweak { if (!_mobStateQuery.Resolve(target, ref component, false)) return false; @@ -82,7 +105,7 @@ public bool IsIncapacitated(EntityUid target, MobStateComponent? component = nul { if (!_mobStateQuery.Resolve(target, ref component, false)) return false; - return component.CurrentState is MobState.Critical or MobState.Dead; + return component.CurrentState is MobState.SoftCritical or MobState.Critical or MobState.Dead; // ADT-Tweak } /// diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs index c9534c13666..9e5ee8286de 100644 --- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs +++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs @@ -175,7 +175,8 @@ public bool TryGetIncapThreshold(EntityUid target, [NotNullWhen(true)] out Fixed if (!Resolve(target, ref thresholdComponent)) return false; - return TryGetThresholdForState(target, MobState.Critical, out threshold, thresholdComponent) + return TryGetThresholdForState(target, MobState.SoftCritical, out threshold, thresholdComponent) // ADT-Tweak + || TryGetThresholdForState(target, MobState.Critical, out threshold, thresholdComponent) || TryGetThresholdForState(target, MobState.Dead, out threshold, thresholdComponent); } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 3052e873d26..a69afee25a9 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -87,6 +87,7 @@ private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobSt { break; } + case MobState.SoftCritical: // ADT-Tweak case MobState.Critical: { _status.TryRemoveStatusEffect(uid, StunId); diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs index 455e6a508ef..7504bbe4bf5 100644 --- a/Content.Shared/Verbs/VerbCategory.cs +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -96,3 +96,4 @@ public VerbCategory(string text, string? icon, bool iconsOnly = false) new("verb-categories-adjust", "/Textures/Interface/VerbIcons/screwdriver.png"); } } + diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/attributions.yml b/Resources/Audio/ADT/Alerts/CatchBreath/attributions.yml new file mode 100644 index 00000000000..c49cd5a56eb --- /dev/null +++ b/Resources/Audio/ADT/Alerts/CatchBreath/attributions.yml @@ -0,0 +1,24 @@ +- files: ["catch-breath-try.ogg"] + license: "CC-BY-3.0" + copyright: "Blood vomit warning by Orsoniks, SCAV Prototype/Casualties Unknown OST. Converted from .ogx to .ogg" + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["catch-breath-success.ogg"] + license: "CC-BY-3.0" + copyright: "Mood up by Orsoniks, SCAV Prototype/Casualties Unknown OST. Converted from .ogx to .ogg" + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["catch-breath-nothing.ogg"] + license: "CC-BY-3.0" + copyright: "Mood down by Orsoniks, SCAV Prototype/Casualties Unknown OST. Converted from .ogx to .ogg" + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["catch-breath-failure.ogg"] + license: "CC-BY-3.0" + copyright: "Self harm by Orsoniks, SCAV Prototype/Casualties Unknown OST. Converted from .ogx to .ogg" + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["catch-breath-bluntsuccess.ogg"] + license: "CC-BY-3.0" + copyright: "Skill up by Orsoniks, SCAV Prototype/Casualties Unknown OST. Converted from .ogx to .ogg" + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-bluntsuccess.ogg b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-bluntsuccess.ogg new file mode 100644 index 00000000000..08bdde70400 Binary files /dev/null and b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-bluntsuccess.ogg differ diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-failure.ogg b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-failure.ogg new file mode 100644 index 00000000000..05d8dfe3749 Binary files /dev/null and b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-failure.ogg differ diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-nothing.ogg b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-nothing.ogg new file mode 100644 index 00000000000..d56142e7aa1 Binary files /dev/null and b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-nothing.ogg differ diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-success.ogg b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-success.ogg new file mode 100644 index 00000000000..aee65662d25 Binary files /dev/null and b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-success.ogg differ diff --git a/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-try.ogg b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-try.ogg new file mode 100644 index 00000000000..9c9940e64d4 Binary files /dev/null and b/Resources/Audio/ADT/Alerts/CatchBreath/catch-breath-try.ogg differ diff --git a/Resources/Audio/ADT/Effects/attributions.yml b/Resources/Audio/ADT/Effects/attributions.yml new file mode 100644 index 00000000000..33ab74b229d --- /dev/null +++ b/Resources/Audio/ADT/Effects/attributions.yml @@ -0,0 +1,14 @@ +- files: ["backtolife.ogg"] + license: "CC-BY-3.0" + copyright: "Last stand drone and heartbeat by Orsoniks, SCAV Prototype/Casualties Unknown OST." + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["soft_critical.ogg"] + license: "CC-BY-3.0" + copyright: "Critical loop by Orsoniks, SCAV Prototype/Casualties Unknown OST." + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" + +- files: ["critical.ogg"] + license: "CC-BY-3.0" + copyright: "Critical loop unconscious by Orsoniks, SCAV Prototype/Casualties Unknown OST." + source: "https://scavprototype.wiki.gg/wiki/Soundtrack" diff --git a/Resources/Audio/ADT/Effects/backtolife.ogg b/Resources/Audio/ADT/Effects/backtolife.ogg new file mode 100644 index 00000000000..4d8503169e6 Binary files /dev/null and b/Resources/Audio/ADT/Effects/backtolife.ogg differ diff --git a/Resources/Audio/ADT/Effects/critical.ogg b/Resources/Audio/ADT/Effects/critical.ogg new file mode 100644 index 00000000000..afa83449875 Binary files /dev/null and b/Resources/Audio/ADT/Effects/critical.ogg differ diff --git a/Resources/Audio/ADT/Effects/soft_critical.ogg b/Resources/Audio/ADT/Effects/soft_critical.ogg new file mode 100644 index 00000000000..804b04df04c Binary files /dev/null and b/Resources/Audio/ADT/Effects/soft_critical.ogg differ diff --git a/Resources/Locale/en-US/ADT/prototypes/entities/Structures/windows.ftl b/Resources/Locale/en-US/ADT/prototypes/entities/Structures/windows.ftl new file mode 100644 index 00000000000..1360d8aae53 --- /dev/null +++ b/Resources/Locale/en-US/ADT/prototypes/entities/Structures/windows.ftl @@ -0,0 +1,2 @@ +ent-ADTWindowPaper = paper window + .desc = A fragile shoji screen of wooden lattice and paper. Fills the room with soft light... and burns beautifully. diff --git a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl index 68e8dd38063..819f429cfdb 100644 --- a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl +++ b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl @@ -6,6 +6,8 @@ health-analyzer-window-entity-unknown-value-text = N/A health-analyzer-window-entity-alive-text = Alive health-analyzer-window-entity-dead-text = Dead health-analyzer-window-entity-critical-text = Critical +# ADT-Tweak +health-analyzer-window-entity-soft-critical-text = Semi-conscious health-analyzer-window-entity-temperature-text = Temperature: health-analyzer-window-entity-blood-level-text = Blood Level: diff --git a/Resources/Locale/en-US/round-end/round-end-summary-window.ftl b/Resources/Locale/en-US/round-end/round-end-summary-window.ftl index 58d26319b32..3df0cbda54f 100644 --- a/Resources/Locale/en-US/round-end/round-end-summary-window.ftl +++ b/Resources/Locale/en-US/round-end/round-end-summary-window.ftl @@ -6,3 +6,56 @@ round-end-summary-window-gamemode-name-label = The game mode was [color=white]{$ round-end-summary-window-duration-label = It lasted for [color=yellow]{$hours} hours, {$minutes} minutes, and {$seconds} seconds. round-end-summary-window-player-info-if-observer-text = [color=gray]{$playerOOCName}[/color] was [color=lightblue]{$playerICName}[/color], an observer. round-end-summary-window-player-info-if-not-observer-text = [color=gray]{$playerOOCName}[/color] was [color={$icNameColor}]{$playerICName}[/color] playing role of [color=orange]{$playerRole}[/color]. + +# ADT +round-end-summary-window-crew-tab-title = Crew +round-end-summary-window-crew-summary = Crew: [color=green]{$alive} alive[/color], [color=red]{$dead} dead[/color], [color=yellow]{$escaped} escaped[/color] ({$total} total) +round-end-summary-window-crew-name-header = Name +round-end-summary-window-crew-role-header = Role +round-end-summary-window-crew-status-header = Status +round-end-summary-window-crew-status-escaped = Escaped +round-end-summary-window-crew-status-alive = Alive +round-end-summary-window-crew-status-dead = Dead +round-end-summary-window-crew-status-nobody = No body +round-end-summary-window-crew-status-observer = Observer + +# ADT +round-end-report-tab-title = Report +round-end-report-yes = Yes +round-end-report-no = No +round-end-report-unknown-name = Unknown + +round-end-report-round-id = Round ID: [color=white][bold]#{$roundId}[/bold][/color] +round-end-report-gamemode = Game mode: [color=white]{$gamemode}[/color] +round-end-report-duration = Shift Duration: [color=yellow]{$hours}h {$minutes}m {$seconds}s[/color] + +round-end-report-category-summary = Shift Summary +round-end-report-category-first-death = First Death +round-end-report-category-economy = Station Economic Summary +round-end-report-category-misc = Miscellaneous +round-end-report-category-census = General Statistics + +round-end-report-station-integrity = Station Integrity: [color=lightblue]{$value}%[/color] +round-end-report-population = Total Population: [color=white]{$value}[/color] +round-end-report-evacuation-rate = Evacuation Rate: [color=lightgreen]{$value}[/color] ({$valuePercent}%) +round-end-report-shuttle-rate = On the emergency shuttle: [color=lightgreen]{$value}[/color] ({$valuePercent}%) +round-end-report-survival-rate = Survival Rate: [color=green]{$value}[/color] ({$valuePercent}%) + +round-end-report-no-deaths = [color=lightgreen]Nobody died this shift![/color] +round-end-report-first-death = [color=red]{$name}[/color], {$job}. Damage taken: [color=red]{$value}[/color] +round-end-report-first-death-last-words = Their last words were: [color=orange]"{$lastWords}"[/color] + +round-end-report-station-vault = Collected by crew this shift: [color=yellow]{$value}[/color] spesos +round-end-report-average-wealth = Average per crewmate: [color=yellow]{$value}[/color] spesos +round-end-report-richest = Most affluent crew member: [color=lightgreen]{$name}[/color], {$job} — [color=yellow]{$value}[/color] spesos ({$player}) +round-end-report-nobody-rich = [color=red]Somehow, nobody made any money this shift![/color] + +round-end-report-ore-mined = Ore mined: [color=orange]{$value}[/color] +round-end-report-food-eaten = Food eaten: [color=white]{$value}[/color] bites/sips +round-end-report-slips = Slips this shift: [color=yellow]{$value}[/color], of which clowns — [color=pink]{$clown}[/color] +round-end-report-clowns-beaten = The clown was beaten [color=pink]{$value}[/color] times +round-end-report-corpses = Corpses on station: [color=red]{$value}[/color] +round-end-report-battered-survivor = Most battered survivor: [color=orange]{$name}[/color], {$job} — [color=red]{$value}[/color] damage ({$player}) + +round-end-report-species-header = Species this shift: [color=white]{$count}[/color] +round-end-report-species-line = {$species} — [color=white]{$count}[/color] diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl index d52b43c9a7d..252b507836a 100644 --- a/Resources/Locale/en-US/verbs/verb-system.ftl +++ b/Resources/Locale/en-US/verbs/verb-system.ftl @@ -35,3 +35,4 @@ verb-common-close = Close verb-common-open = Open verb-common-close-ui = Close UI verb-common-open-ui = Open UI + diff --git a/Resources/Locale/ru-RU/ADT/alerts/alerts.ftl b/Resources/Locale/ru-RU/ADT/alerts/alerts.ftl index a499d0f4313..f8d40ca3f0b 100644 --- a/Resources/Locale/ru-RU/ADT/alerts/alerts.ftl +++ b/Resources/Locale/ru-RU/ADT/alerts/alerts.ftl @@ -1,5 +1,7 @@ alerts-crawling-name = Ползание alerts-crawling-desc = Вы ползёте, нажмите С чтобы встать. +alerts-adt-soft-crit-name = [color=red]Полу-осознанное состояние[/color] +alerts-adt-soft-crit-desc = Вы серьёзно ранены и чудом всё ещё в сознании, вы можете только ползти и шептать. Вы можете нажать, чтобы попробовать отдышаться. alerts-polymorph-name = [color=#62278c]Полиморф[/color] alerts-polymorph-desc = [color=#b26de3]С вашим телом происходит нечто странное...[/color] alerts-offer-name = Получить diff --git a/Resources/Locale/ru-RU/ADT/mobs/catchbreath.ftl b/Resources/Locale/ru-RU/ADT/mobs/catchbreath.ftl new file mode 100644 index 00000000000..330d7ce552f --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/mobs/catchbreath.ftl @@ -0,0 +1,5 @@ +catch-breath-try = Ты пытаешься отдышаться... +catch-breath-blunt-success = Ты с трудом собираешься... и боль немного отступает. +catch-breath-success = Ты делаешь судорожный вдох. +catch-breath-failure = Ты не можешь вдохнуть! +catch-breath-nothing = Ты сделал очень слабый вдох, легче не стало, но и хуже тоже. diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/Flora/trees.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/Flora/trees.ftl index 25c89bd11e4..e5d0c069134 100644 --- a/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/Flora/trees.ftl +++ b/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/Flora/trees.ftl @@ -22,4 +22,7 @@ ent-ADTFloraPalmTree02 = { ent-ADTFloraPalmTree01 } ent-ADTFloraPalmTree03 = { ent-ADTFloraPalmTree01 } .desc = { ent-ADTFloraPalmTree01.desc } +ent-ADTFloraTreeForgotten = забытое дерево + .desc = Там кто-то есть за деревом. + diff --git a/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/windows.ftl b/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/windows.ftl new file mode 100644 index 00000000000..bf80b488cb0 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/prototypes/Entities/Structures/windows.ftl @@ -0,0 +1,2 @@ +ent-ADTWindowPaper = бумажное окно + .desc = Хрупкая сёдзи из деревянной рамы и бумаги. Наполняет комнату мягким светом... и прекрасно горит. diff --git a/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl b/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl index ac19834aa8a..6f4e3095b90 100644 --- a/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl +++ b/Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl @@ -5,6 +5,8 @@ health-analyzer-window-entity-unknown-value-text = Н/Д health-analyzer-window-entity-alive-text = Жив health-analyzer-window-entity-dead-text = Мёртв health-analyzer-window-entity-critical-text = Критическое состояние +# ADT-Tweak +health-analyzer-window-entity-soft-critical-text = Полу-осознанное состояние health-analyzer-window-entity-temperature-text = Температура: health-analyzer-window-entity-status-text = Статус: health-analyzer-window-entity-blood-level-text = Уровень крови: diff --git a/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl b/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl index b330261930c..0aad1e37241 100644 --- a/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl +++ b/Resources/Locale/ru-RU/round-end/round-end-summary-window.ftl @@ -6,3 +6,56 @@ round-end-summary-window-gamemode-name-label = Игровой режим был round-end-summary-window-duration-label = Он длился [color=yellow]{ $hours } ч., { $minutes } мин., и { $seconds } сек. round-end-summary-window-player-info-if-observer-text = [color=gray]{ $playerOOCName }[/color] был [color=lightblue]{ $playerICName }[/color], наблюдатель. round-end-summary-window-player-info-if-not-observer-text = [color=gray]{ $playerOOCName }[/color] был [color={ $icNameColor }]{ $playerICName }[/color], в роли [color=orange]{ $playerRole }[/color]. + +# ADT +round-end-summary-window-crew-tab-title = Экипаж +round-end-summary-window-crew-summary = Экипаж: [color=green]{$alive} жив[/color], [color=red]{$dead} мёртв[/color], [color=yellow]{$escaped} эвакуировался[/color] (всего {$total}) +round-end-summary-window-crew-name-header = Имя +round-end-summary-window-crew-role-header = Должность +round-end-summary-window-crew-status-header = Статус +round-end-summary-window-crew-status-escaped = Эвакуировался +round-end-summary-window-crew-status-alive = Выжил +round-end-summary-window-crew-status-dead = Мёртв +round-end-summary-window-crew-status-nobody = Нет тела +round-end-summary-window-crew-status-observer = Наблюдатель + +# ADT +round-end-report-tab-title = Отчёт +round-end-report-yes = Да +round-end-report-no = Нет +round-end-report-unknown-name = Неизвестно + +round-end-report-round-id = Раунд: [color=white][bold]#{ $roundId }[/bold][/color] +round-end-report-gamemode = Игровой режим: [color=white]{ $gamemode }[/color] +round-end-report-duration = Длительность смены: [color=yellow]{ $hours } ч. { $minutes } мин. { $seconds } сек.[/color] + +round-end-report-category-summary = Итоги смены +round-end-report-category-first-death = Первая смерть +round-end-report-category-economy = Экономическая сводка станции +round-end-report-category-misc = Прочее +round-end-report-category-census = Общая статистика + +round-end-report-station-integrity = Целостность станции: [color=lightblue]{ $value }%[/color] +round-end-report-population = Общая численность: [color=white]{ $value }[/color] +round-end-report-evacuation-rate = Эвакуировалось: [color=lightgreen]{ $value }[/color] ({ $valuePercent }%) +round-end-report-shuttle-rate = Из них на шаттле эвакуации: [color=lightgreen]{ $value }[/color] ({ $valuePercent }%) +round-end-report-survival-rate = Выжило: [color=green]{ $value }[/color] ({ $valuePercent }%) + +round-end-report-no-deaths = [color=lightgreen]В эту смену никто не погиб![/color] +round-end-report-first-death = [color=red]{ $name }[/color], { $job }. Получено урона: [color=red]{ $value }[/color] +round-end-report-first-death-last-words = Последние слова: [color=orange]"{ $lastWords }"[/color] + +round-end-report-station-vault = Экипаж собрал за смену: [color=yellow]{ $value }[/color] кредитов +round-end-report-average-wealth = В среднем на человека: [color=yellow]{ $value }[/color] кредитов +round-end-report-richest = Самый богатый член экипажа: [color=lightgreen]{ $name }[/color], { $job } — [color=yellow]{ $value }[/color] кредитов ({ $player }) +round-end-report-nobody-rich = [color=red]Каким-то образом никто не заработал за смену ни одного кредита...[/color] + +round-end-report-ore-mined = Добыто руды: [color=orange]{ $value }[/color] +round-end-report-food-eaten = Съедено еды: [color=white]{ $value }[/color] укусов/глотков +round-end-report-slips = Подскользнулись: [color=yellow]{ $value }[/color] раз, из них клоуны — [color=pink]{ $clown }[/color] +round-end-report-clowns-beaten = Клоуна избили: [color=pink]{ $value }[/color] раз +round-end-report-corpses = Трупов на станции: [color=red]{ $value }[/color] +round-end-report-battered-survivor = Самый потрёпанный из выживших: [color=orange]{ $name }[/color], { $job } — [color=red]{ $value }[/color] урона ({ $player }) + +round-end-report-species-header = Рас на смене: [color=white]{ $count }[/color] +round-end-report-species-line = { $species } — [color=white]{ $count }[/color] diff --git a/Resources/Locale/ru-RU/verbs/verb-system.ftl b/Resources/Locale/ru-RU/verbs/verb-system.ftl index a2453057c39..40737ac4f99 100644 --- a/Resources/Locale/ru-RU/verbs/verb-system.ftl +++ b/Resources/Locale/ru-RU/verbs/verb-system.ftl @@ -31,3 +31,4 @@ verb-common-close = Закрыть verb-common-open = Открыть verb-common-close-ui = Закрыть UI verb-common-open-ui = Открыть UI + diff --git a/Resources/Prototypes/ADT/Alerts/alerts.yml b/Resources/Prototypes/ADT/Alerts/alerts.yml index 1a36b3c90ad..f265a8c0e0b 100644 --- a/Resources/Prototypes/ADT/Alerts/alerts.yml +++ b/Resources/Prototypes/ADT/Alerts/alerts.yml @@ -27,6 +27,26 @@ name: alerts-crawling-name description: alerts-crawling-desc + +- type: alert + id: ADTHumanSoftCrit + category: Health + clickEvent: !type:TryCatchBreathAlertEvent + icons: + - sprite: /Textures/Interface/Alerts/human_critical.rsi + state: critical + name: alerts-adt-soft-crit-name + description: alerts-adt-soft-crit-desc + +- type: alert + id: ADTBorgSoftCrit + category: Health + icons: + - sprite: /Textures/Interface/Alerts/borg_critical.rsi + state: critical + name: alerts-adt-soft-crit-name + description: alerts-adt-soft-crit-desc + - type: alert id: ADTAlertPolymorph icons: diff --git a/Resources/Prototypes/ADT/Body/Species/drask.yml b/Resources/Prototypes/ADT/Body/Species/drask.yml index 3c3b0520dc9..81f00b664f6 100644 --- a/Resources/Prototypes/ADT/Body/Species/drask.yml +++ b/Resources/Prototypes/ADT/Body/Species/drask.yml @@ -123,7 +123,8 @@ - type: MobThresholds thresholds: 0: Alive - 110: Critical + 110: SoftCritical + 165: Critical 220: Dead - type: Barotrauma damage: diff --git a/Resources/Prototypes/ADT/Body/Species/felinid.yml b/Resources/Prototypes/ADT/Body/Species/felinid.yml index d1a474b7a96..c3bf60b9f0d 100644 --- a/Resources/Prototypes/ADT/Body/Species/felinid.yml +++ b/Resources/Prototypes/ADT/Body/Species/felinid.yml @@ -128,7 +128,8 @@ - type: MobThresholds thresholds: 0: Alive - 100: Critical + 100: SoftCritical + 142: Critical 185: Dead - type: SizeAttributeWhitelist tall: true diff --git a/Resources/Prototypes/ADT/Body/Species/kobalt.yml b/Resources/Prototypes/ADT/Body/Species/kobalt.yml index 35f79fcb777..bf2cef3defd 100644 --- a/Resources/Prototypes/ADT/Body/Species/kobalt.yml +++ b/Resources/Prototypes/ADT/Body/Species/kobalt.yml @@ -95,7 +95,8 @@ - type: MobThresholds thresholds: 0: Alive - 85: Critical + 85: SoftCritical + 132: Critical 180: Dead - type: Damageable damageModifierSet: Kobalt diff --git a/Resources/Prototypes/ADT/Body/Species/novakid.yml b/Resources/Prototypes/ADT/Body/Species/novakid.yml index a4a151afe87..2f106c1efc2 100644 --- a/Resources/Prototypes/ADT/Body/Species/novakid.yml +++ b/Resources/Prototypes/ADT/Body/Species/novakid.yml @@ -147,7 +147,8 @@ - type: MobThresholds thresholds: 0: Alive - 110: Critical + 110: SoftCritical + 217: Critical 325: Dead - type: Explosive explosionType: Default diff --git a/Resources/Prototypes/ADT/Body/Species/resomi.yml b/Resources/Prototypes/ADT/Body/Species/resomi.yml index d6ed8d28a80..6868127488f 100644 --- a/Resources/Prototypes/ADT/Body/Species/resomi.yml +++ b/Resources/Prototypes/ADT/Body/Species/resomi.yml @@ -218,7 +218,8 @@ - type: MobThresholds thresholds: 0: Alive - 85: Critical + 85: SoftCritical + 117: Critical 150: Dead - type: Butcherable butcheringType: Spike diff --git a/Resources/Prototypes/ADT/Body/Species/shadekin.yml b/Resources/Prototypes/ADT/Body/Species/shadekin.yml index aa966b2b05c..7dd9b1be000 100644 --- a/Resources/Prototypes/ADT/Body/Species/shadekin.yml +++ b/Resources/Prototypes/ADT/Body/Species/shadekin.yml @@ -209,7 +209,8 @@ - type: MobThresholds thresholds: 0: Alive - 80: Critical + 80: SoftCritical + 120: Critical 160: Dead - type: SlowOnDamage speedModifierThresholds: diff --git a/Resources/Prototypes/ADT/Body/Species/tajaran.yml b/Resources/Prototypes/ADT/Body/Species/tajaran.yml index cf83f82bf3b..5b34db44186 100644 --- a/Resources/Prototypes/ADT/Body/Species/tajaran.yml +++ b/Resources/Prototypes/ADT/Body/Species/tajaran.yml @@ -146,7 +146,8 @@ - type: MobThresholds thresholds: 0: Alive - 90: Critical + 90: SoftCritical + 145: Critical 200: Dead - type: RoarAccent - type: Damageable diff --git a/Resources/Prototypes/ADT/Body/Species/ursus.yml b/Resources/Prototypes/ADT/Body/Species/ursus.yml index 5cf2fc64b5b..ffa5d40e723 100644 --- a/Resources/Prototypes/ADT/Body/Species/ursus.yml +++ b/Resources/Prototypes/ADT/Body/Species/ursus.yml @@ -124,7 +124,8 @@ - type: MobThresholds thresholds: 0: Alive - 115: Critical + 115: SoftCritical + 182: Critical 250: Dead - type: Barotrauma damage: diff --git a/Resources/Prototypes/ADT/Entities/Structures/Flora/trees.yml b/Resources/Prototypes/ADT/Entities/Structures/Flora/trees.yml index 2aa7187d668..6e60d334b16 100644 --- a/Resources/Prototypes/ADT/Entities/Structures/Flora/trees.yml +++ b/Resources/Prototypes/ADT/Entities/Structures/Flora/trees.yml @@ -1,4 +1,4 @@ -# Сломанные деревья +# Сломанные деревья - type: entity parent: BaseTree id: ADTFloraTreeBroken01 @@ -124,3 +124,30 @@ - type: Sprite state: treepalm03 +- type: entity + parent: BaseTree + id: ADTFloraTreeForgotten + name: forgotten tree + description: Well, there is a man here. + components: + - type: Sprite + sprite: ADT/Structures/Flora/flora_forgotten_tree.rsi + state: treeforgotten + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.35,-0.3,0.35,0.5" + density: 1000 + layer: + - WallLayer + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + diff --git a/Resources/Prototypes/ADT/Entities/Structures/Windows/paper_window.yml b/Resources/Prototypes/ADT/Entities/Structures/Windows/paper_window.yml new file mode 100644 index 00000000000..50a8f5f7ccf --- /dev/null +++ b/Resources/Prototypes/ADT/Entities/Structures/Windows/paper_window.yml @@ -0,0 +1,72 @@ +# ADT: бумажные окна (сёдзи) в стиле SS13 — для постройки додзё +- type: entity + id: ADTWindowPaper + parent: BaseStructure + name: paper window + description: A fragile shoji screen of wooden lattice and paper. Fills the room with soft light... and burns beautifully. + placement: + mode: SnapgridCenter + snap: + - Window + components: + - type: Anchorable + flags: + - Anchorable + - type: Rotatable + - type: Tag + tags: + - ForceFixRotations + - Window + - type: Sprite + drawdepth: WallTops + sprite: ADT/Structures/Windows/paperwindow.rsi + state: full + - type: Icon + sprite: ADT/Structures/Windows/paperwindow.rsi + state: full + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb {} + mask: + - FullTileMask + layer: + - GlassLayer + - type: Damageable + damageModifierSet: Wood + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 30 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: WoodDestroy + - !type:SpawnEntitiesBehavior + spawn: + Paper: + min: 1 + max: 2 + MaterialWoodPlank1: + min: 1 + max: 1 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Airtight + - type: Flammable + damage: + types: + Heat: 3 + - type: MeleeSound + soundGroups: + Brute: + path: /Audio/Weapons/boxingpunch1.ogg + - type: RCDDeconstructable + cost: 4 + delay: 2 + fx: EffectRCDDeconstruct2 + - type: Repairable diff --git a/Resources/Prototypes/ADT/Shadowling/actions.yml b/Resources/Prototypes/ADT/Shadowling/actions.yml index 19a502599e2..17bcf981fca 100644 --- a/Resources/Prototypes/ADT/Shadowling/actions.yml +++ b/Resources/Prototypes/ADT/Shadowling/actions.yml @@ -327,6 +327,8 @@ breakAllLights: true sound: path: /Audio/ADT/Shadowling/hilarious_agony.ogg + params: + volume: -8 - type: entity id: ADTActionAscendantAnnihilate diff --git a/Resources/Prototypes/Body/Species/skeleton.yml b/Resources/Prototypes/Body/Species/skeleton.yml index 2d75ae25bfa..eee3b212ac1 100644 --- a/Resources/Prototypes/Body/Species/skeleton.yml +++ b/Resources/Prototypes/Body/Species/skeleton.yml @@ -88,7 +88,8 @@ - type: MobThresholds thresholds: 0: Alive - 100: Critical + 100: SoftCritical # ADT-Tweak + 125: Critical # ADT-Tweak 150: Dead - type: TransferMindOnGib - type: Destructible diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 26dfd72e88c..fef2e435afc 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -139,7 +139,8 @@ - type: MobThresholds thresholds: 0: Alive - 100: Critical + 100: SoftCritical # ADT-Tweak + 150: Critical # ADT-Tweak 200: Dead - type: MobStateActions actions: diff --git a/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/meta.json b/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/meta.json new file mode 100644 index 00000000000..7b1f5e2b8b5 --- /dev/null +++ b/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/meta.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Forgotten tree from Deltarune (Toby Fox), ported for Adventure Time", + "size": { + "x": 128, + "y": 83 + }, + "states": [ + { + "name": "treeforgotten", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4 + ] + ], + "flags": { + "loop": true + } + } + ] +} diff --git a/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/treeforgotten.png b/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/treeforgotten.png new file mode 100644 index 00000000000..9f86868c0b4 Binary files /dev/null and b/Resources/Textures/ADT/Structures/Flora/flora_forgotten_tree.rsi/treeforgotten.png differ diff --git a/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/full.png b/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/full.png new file mode 100644 index 00000000000..07e14c2615d Binary files /dev/null and b/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/full.png differ diff --git a/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/meta.json b/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/meta.json new file mode 100644 index 00000000000..73f49e800f9 --- /dev/null +++ b/Resources/Textures/ADT/Structures/Windows/paperwindow.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Shoji paper window sprite generated for Adventure Time", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "full" + } + ] +} \ No newline at end of file