diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs index 44c40143d83..04075000f5b 100644 --- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs +++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs @@ -39,6 +39,6 @@ protected override void UpdateState(BoundUserInterfaceState message) if (message is not CargoBountyConsoleState state) return; - _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip); + _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip); } } diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml new file mode 100644 index 00000000000..eee8c5cc165 --- /dev/null +++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs new file mode 100644 index 00000000000..f3c9bbfafb1 --- /dev/null +++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs @@ -0,0 +1,54 @@ +using Content.Client.Message; +using Content.Shared.Cargo; +using Content.Shared.Cargo.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.Cargo.UI; + +[GenerateTypedNameReferences] +public sealed partial class BountyHistoryEntry : BoxContainer +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public BountyHistoryEntry(CargoBountyHistoryData bounty) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype)) + return; + + var items = new List(); + foreach (var entry in bountyPrototype.Entries) + { + items.Add(Loc.GetString("bounty-console-manifest-entry", + ("amount", entry.Amount), + ("item", Loc.GetString(entry.Name)))); + } + ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items)))); + RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward))); + IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id))); + + var stationTime = bounty.Timestamp.ToString("hh\\:mm\\:ss"); + if (bounty.ActorName == null) + { + StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-completed-label")); + NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label", ("time", stationTime))); + } + else + { + StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-skipped-label")); + NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label", + ("id", bounty.ActorName), + ("time", stationTime))); + } + } + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + } +} diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml b/Content.Client/Cargo/UI/CargoBountyMenu.xaml index bb263ff6c4a..0f093d5f8e7 100644 --- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml +++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml @@ -11,15 +11,26 @@ - - - - + + + + + + + + + + diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs index 3767b45e4be..0717aacc5e6 100644 --- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs +++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs @@ -17,8 +17,11 @@ public CargoBountyMenu() RobustXamlLoader.Load(this); } - public void UpdateEntries(List bounties, TimeSpan untilNextSkip) + public void UpdateEntries(List bounties, List history, TimeSpan untilNextSkip) { + MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label")); + MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label")); + BountyEntriesContainer.Children.Clear(); foreach (var b in bounties) { @@ -32,5 +35,12 @@ public void UpdateEntries(List bounties, TimeSpan untilNextSkip { MinHeight = 10 }); + + BountyHistoryContainer.Children.Clear(); + foreach (var h in history) + { + var entry = new BountyHistoryEntry(h); + BountyHistoryContainer.AddChild(entry); + } } } diff --git a/Content.Client/Stack/StackSystem.cs b/Content.Client/Stack/StackSystem.cs index 7e681aeba3e..f57f4d4e170 100644 --- a/Content.Client/Stack/StackSystem.cs +++ b/Content.Client/Stack/StackSystem.cs @@ -8,7 +8,7 @@ namespace Content.Client.Stack { [UsedImplicitly] - public sealed class StackSystem : SharedStackSystem + public sealed partial class StackSystem : SharedStackSystem // Frontier: add partial to class definition { [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; [Dependency] private readonly ItemCounterSystem _counterSystem = default!; @@ -44,7 +44,7 @@ public override void SetCount(EntityUid uid, int amount, StackComponent? compone // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call. if (component.Count <= 0 && !component.Lingering) { - Xform.DetachEntity(uid, Transform(uid)); + Xform.DetachParentToNull(uid, Transform(uid)); return; } @@ -56,20 +56,25 @@ private void OnAppearanceChange(EntityUid uid, StackComponent comp, ref Appearan if (args.Sprite == null || comp.LayerStates.Count < 1) return; + StackLayerData data = new StackLayerData(); // Frontier: use structure to store StackLayerData + // Skip processing if no actual - if (!_appearanceSystem.TryGetData(uid, StackVisuals.Actual, out var actual, args.Component)) + if (!_appearanceSystem.TryGetData(uid, StackVisuals.Actual, out data.Actual, args.Component)) return; - if (!_appearanceSystem.TryGetData(uid, StackVisuals.MaxCount, out var maxCount, args.Component)) - maxCount = comp.LayerStates.Count; + if (!_appearanceSystem.TryGetData(uid, StackVisuals.MaxCount, out data.MaxCount, args.Component)) + data.MaxCount = comp.LayerStates.Count; + + if (!_appearanceSystem.TryGetData(uid, StackVisuals.Hide, out data.Hidden, args.Component)) + data.Hidden = false; - if (!_appearanceSystem.TryGetData(uid, StackVisuals.Hide, out var hidden, args.Component)) - hidden = false; + if (comp.LayerFunction != StackLayerFunction.None) // Frontier: use stack layer function to modify appearance if provided. + ApplyLayerFunction(uid, comp, ref data); // Frontier: definition in _NF/Stack/StackSystem.Layers.cs if (comp.IsComposite) - _counterSystem.ProcessCompositeSprite(uid, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite); + _counterSystem.ProcessCompositeSprite(uid, data.Actual, data.MaxCount, comp.LayerStates, data.Hidden, sprite: args.Sprite); else - _counterSystem.ProcessOpaqueSprite(uid, comp.BaseLayer, actual, maxCount, comp.LayerStates, hidden, sprite: args.Sprite); + _counterSystem.ProcessOpaqueSprite(uid, comp.BaseLayer, data.Actual, data.MaxCount, comp.LayerStates, data.Hidden, sprite: args.Sprite); } } } diff --git a/Content.Client/_NF/Stack/StackSystem.Layers.cs b/Content.Client/_NF/Stack/StackSystem.Layers.cs new file mode 100644 index 00000000000..2893d32d3f5 --- /dev/null +++ b/Content.Client/_NF/Stack/StackSystem.Layers.cs @@ -0,0 +1,56 @@ +using Content.Shared.Stacks.Components; +using Content.Shared.Stacks; + +namespace Content.Client.Stack +{ + /// + /// Data used to determine which layers of a stack's sprite are visible. + /// + public struct StackLayerData + { + public int Actual; + public int MaxCount; + public bool Hidden; + } + + public sealed partial class StackSystem : SharedStackSystem + { + // Modifies a given stack component to adjust the layers to display. + private bool ApplyLayerFunction(EntityUid uid, StackComponent comp, ref StackLayerData data) + { + switch (comp.LayerFunction) + { + case StackLayerFunction.Threshold: + if (TryComp(uid, out var threshold)) + { + ApplyThreshold(threshold, ref data); + return true; + } + break; + } + // No function applied. + return false; + } + + /// + /// Sets Actual to the number of thresholds that Actual exceeds from the beginning of the list. + /// Sets MaxCount to the total number of thresholds plus one (for values under thresholds). + /// + private static void ApplyThreshold(StackLayerThresholdComponent comp, ref StackLayerData data) + { + // We must stop before we run out of thresholds or layers, whichever's smaller. + data.MaxCount = Math.Min(comp.Thresholds.Count + 1, data.MaxCount); + int newActual = 0; + foreach (var threshold in comp.Thresholds) + { + //If our value exceeds threshold, the next layer should be displayed. + //Note: we must ensure actual <= MaxCount. + if (data.Actual >= threshold && newActual < data.MaxCount) + newActual++; + else + break; + } + data.Actual = newActual; + } + } +} diff --git a/Content.Server/Body/Components/LungComponent.cs b/Content.Server/Body/Components/LungComponent.cs index 72af4d9e63a..256c6d67298 100644 --- a/Content.Server/Body/Components/LungComponent.cs +++ b/Content.Server/Body/Components/LungComponent.cs @@ -34,4 +34,11 @@ public sealed partial class LungComponent : Component /// [DataField] public ProtoId Alert = "LowOxygen"; + + /// + /// DeltaV: Multiplier on saturation passively lost. + /// Higher values require more air, lower require less. + /// + [DataField] + public float SaturationLoss = 1f; } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 1f95295d2f8..6cb575a6888 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -78,14 +78,15 @@ public override void Update(float frameTime) if (_mobState.IsDead(uid)) continue; - //_bodySystem.GetBodyOrgans<(EntityUid euid, LungComponent lc)>(uid, body); - //_bodySystem. - /* var organs = _bodySystem.GetBodyOrganComponents(uid, body); - if(organs==null){ - continue; - }*/ - - UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator); + // Begin DeltaV Code: Addition: + var organs = _bodySystem.GetBodyOrganEntityComps((uid, body)); + var multiplier = -1f; + foreach (var (_, lung, _) in organs) + { + multiplier *= lung.SaturationLoss; + } + // End DeltaV Code + UpdateSaturation(uid, multiplier * (float) respirator.UpdateInterval.TotalSeconds, respirator); // DeltaV: use multiplier instead of negating if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. { diff --git a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs index a7735787cbb..f58b5450ba4 100644 --- a/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs +++ b/Content.Server/Cargo/Components/StationCargoBountyDatabaseComponent.cs @@ -21,6 +21,13 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public List Bounties = new(); + /// + /// A list of all the bounties that have been completed or + /// skipped for a station. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List History = new(); + /// /// Used to determine unique order IDs /// diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 373e8e243ba..236b018a9da 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -8,6 +8,7 @@ using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Prototypes; using Content.Shared.Database; +using Content.Shared.IdentityManagement; using Content.Shared.NameIdentifier; using Content.Shared.Paper; using Content.Shared.Stacks; @@ -16,6 +17,7 @@ using Robust.Server.Containers; using Robust.Shared.Containers; using Robust.Shared.Random; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server.Cargo.Systems; @@ -25,6 +27,7 @@ public sealed partial class CargoSystem [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [ValidatePrototypeId] private const string BountyNameIdentifierGroup = "Bounty"; @@ -54,7 +57,7 @@ private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent co return; var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime; - _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip)); + _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip)); } private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args) @@ -95,13 +98,13 @@ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent comp return; } - if (!TryRemoveBounty(station, bounty.Value)) + if (!TryRemoveBounty(station, bounty.Value, null, args.Actor)) return; FillBountyDatabase(station); db.NextSkipTime = _timing.CurTime + db.SkipDelay; var untilNextSkip = db.NextSkipTime - _timing.CurTime; - _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); + _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip)); _audio.PlayPvs(component.SkipSound, uid); } @@ -434,15 +437,15 @@ public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCarg } [PublicAPI] - public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null) + public bool TryRemoveBounty(EntityUid uid, string dataId, StationCargoBountyDatabaseComponent? component = null, EntityUid? actor = null) { if (!TryGetBountyFromId(uid, dataId, out var data, component)) return false; - return TryRemoveBounty(uid, data.Value, component); + return TryRemoveBounty(uid, data.Value, component, actor); } - public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null) + public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBountyDatabaseComponent? component = null, EntityUid? actor = null) { if (!Resolve(uid, ref component)) return false; @@ -451,6 +454,15 @@ public bool TryRemoveBounty(EntityUid uid, CargoBountyData data, StationCargoBou { if (component.Bounties[i].Id == data.Id) { + string? actorName = default; + if (actor != null) + { + var getIdentityEvent = new TryGetIdentityShortInfoEvent(uid, actor.Value); + RaiseLocalEvent(getIdentityEvent); + actorName = getIdentityEvent.Title; + } + + component.History.Add(new CargoBountyHistoryData(data, _gameTiming.CurTime, actorName)); component.Bounties.RemoveAt(i); return true; } @@ -492,7 +504,7 @@ public void UpdateBountyConsoles() } var untilNextSkip = db.NextSkipTime - _timing.CurTime; - _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); + _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip)); } } diff --git a/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs b/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs index ec800db2a30..079ec77bbf3 100644 --- a/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs +++ b/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.ActionBlocker; using Content.Shared.Damage; +using Content.Shared.DeltaV.Carrying; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Mobs.Systems; @@ -11,7 +12,6 @@ using Content.Shared.Verbs; using Content.Shared.Whitelist; using Content.Server.NPC.Components; -using Content.Server.Carrying; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; using Robust.Shared.Utility; diff --git a/Content.Server/DeltaV/Station/Components/CaptainStateComponent.cs b/Content.Server/DeltaV/Station/Components/CaptainStateComponent.cs index 96d7c441071..d432e9ea6d0 100644 --- a/Content.Server/DeltaV/Station/Components/CaptainStateComponent.cs +++ b/Content.Server/DeltaV/Station/Components/CaptainStateComponent.cs @@ -18,7 +18,7 @@ public sealed partial class CaptainStateComponent : Component /// Assume no captain unless specified /// [DataField] - public bool HasCaptain; + public bool HasCaptain = false; /// /// The localization ID used for announcing the cancellation of ACO requests @@ -42,13 +42,13 @@ public sealed partial class CaptainStateComponent : Component /// Set after ACO has been requested to avoid duplicate calls /// [DataField] - public bool IsACORequestActive; + public bool IsACORequestActive = false; /// /// Used to denote that AA has been brought into the round either from captain or safe. /// [DataField] - public bool IsAAInPlay; + public bool IsAAInPlay = false; /// /// The localization ID for announcing that AA has been unlocked for ACO diff --git a/Content.Server/DeltaV/Station/Systems/CaptainStateSystem.cs b/Content.Server/DeltaV/Station/Systems/CaptainStateSystem.cs index 38475da89be..c790b22f782 100644 --- a/Content.Server/DeltaV/Station/Systems/CaptainStateSystem.cs +++ b/Content.Server/DeltaV/Station/Systems/CaptainStateSystem.cs @@ -46,6 +46,12 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var station, out var captainState)) { + if (currentTime < _acoDelay && captainState.IsACORequestActive == true) // Avoid timing issues. No need to run before _acoDelay is reached anyways. + { + Log.Error($"{captainState} IsACORequestActive true before ACO request time."); + captainState.IsACORequestActive = false; + } + if (captainState.HasCaptain) HandleHasCaptain(station, captainState); else diff --git a/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs b/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs new file mode 100644 index 00000000000..11a29e05cc8 --- /dev/null +++ b/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs @@ -0,0 +1,82 @@ +using Content.Shared.Damage; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Weather; +using Content.Shared.Whitelist; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.DeltaV.Weather; + +/// +/// Handles weather damage for exposed entities. +/// +public sealed partial class WeatherEffectsSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly SharedWeatherSystem _weather = default!; + + private EntityQuery _gridQuery; + + public override void Initialize() + { + base.Initialize(); + + _gridQuery = GetEntityQuery(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var map, out var weather)) + { + if (now < weather.NextUpdate) + continue; + + weather.NextUpdate = now + weather.UpdateDelay; + + foreach (var (id, data) in weather.Weather) + { + // start and end do no damage + if (data.State != WeatherState.Running) + continue; + + UpdateDamage(map, id); + } + } + } + + private void UpdateDamage(EntityUid map, ProtoId id) + { + var weather = _proto.Index(id); + if (weather.Damage is not {} damage) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var mob, out var xform)) + { + // don't give dead bodies 10000 burn, that's not fun for anyone + if (xform.MapUid != map || mob.CurrentState == MobState.Dead) + continue; + + // if not in space, check for being indoors + if (xform.GridUid is {} gridUid && _gridQuery.TryComp(gridUid, out var grid)) + { + var tile = _map.GetTileRef((gridUid, grid), xform.Coordinates); + if (!_weather.CanWeatherAffect(gridUid, grid, tile)) + continue; + } + + if (_whitelist.IsBlacklistFailOrNull(weather.DamageBlacklist, uid)) + _damageable.TryChangeDamage(uid, damage, interruptsDoAfters: false); + } + } +} diff --git a/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs b/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs new file mode 100644 index 00000000000..ac69c957057 --- /dev/null +++ b/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs @@ -0,0 +1,58 @@ +using Content.Shared.Destructible.Thresholds; +using Content.Shared.Weather; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.DeltaV.Weather; + +/// +/// Makes weather randomly happen every so often. +/// +[RegisterComponent, Access(typeof(WeatherSchedulerSystem))] +[AutoGenerateComponentPause] +public sealed partial class WeatherSchedulerComponent : Component +{ + /// + /// Weather stages to schedule. + /// + [DataField(required: true)] + public List Stages = new(); + + /// + /// The index of to use next, wraps back to the start. + /// + [DataField] + public int Stage; + + /// + /// When to go to the next step of the schedule. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan NextUpdate; +} + +/// +/// A stage in a weather schedule. +/// +[Serializable, DataDefinition] +public partial struct WeatherStage +{ + /// + /// A range of how long the stage can last for, in seconds. + /// + [DataField(required: true)] + public MinMax Duration = new(0, 0); + + /// + /// The weather prototype to add, or null for clear weather. + /// + [DataField] + public ProtoId? Weather; + + /// + /// Alert message to send in chat for players on the map when it starts. + /// + [DataField] + public LocId? Message; +} diff --git a/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs b/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs new file mode 100644 index 00000000000..a19a2f3787d --- /dev/null +++ b/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs @@ -0,0 +1,75 @@ +using Content.Server.Chat.Managers; +using Content.Shared.Chat; +using Content.Shared.Weather; +using Robust.Shared.Map.Components; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.DeltaV.Weather; + +public sealed class WeatherSchedulerSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedWeatherSystem _weather = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var map, out var comp)) + { + if (now < comp.NextUpdate) + continue; + + if (comp.Stage >= comp.Stages.Count) + comp.Stage = 0; + + var stage = comp.Stages[comp.Stage++]; + var duration = stage.Duration.Next(_random); + comp.NextUpdate = now + TimeSpan.FromSeconds(duration); + + var mapId = Comp(map).MapId; + if (stage.Weather is {} weather) + { + var ending = comp.NextUpdate; + // crossfade weather so as one ends the next starts + if (HasWeather(comp, comp.Stage - 1)) + ending += WeatherComponent.ShutdownTime; + if (HasWeather(comp, comp.Stage + 1)) + ending += WeatherComponent.StartupTime; + _weather.SetWeather(mapId, _proto.Index(weather), ending); + } + + if (stage.Message is {} message) + { + var msg = Loc.GetString(message); + _chat.ChatMessageToManyFiltered( + Filter.BroadcastMap(mapId), + ChatChannel.Radio, + msg, + msg, + map, + false, + true, + null); + } + } + } + + private bool HasWeather(WeatherSchedulerComponent comp, int stage) + { + if (stage < 0) + stage = comp.Stages.Count + stage; + else if (stage >= comp.Stages.Count) + stage %= comp.Stages.Count; + + return comp.Stages[stage].Weather != null; + } +} diff --git a/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs b/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs deleted file mode 100644 index afc78978c86..00000000000 --- a/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.Carrying -{ - /// - /// Stores the carrier of an entity being carried. - /// - [RegisterComponent] - public sealed partial class BeingCarriedComponent : Component - { - public EntityUid Carrier = default!; - } -} diff --git a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs b/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs deleted file mode 100644 index f4fd1fa6d56..00000000000 --- a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading; - -namespace Content.Server.Carrying -{ - [RegisterComponent] - public sealed partial class CarriableComponent : Component - { - /// - /// Number of free hands required - /// to carry the entity - /// - [DataField("freeHandsRequired")] - public int FreeHandsRequired = 2; - - public CancellationTokenSource? CancelToken; - } -} diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs b/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs deleted file mode 100644 index e79460595b9..00000000000 --- a/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.Carrying -{ - /// - /// Added to an entity when they are carrying somebody. - /// - [RegisterComponent] - public sealed partial class CarryingComponent : Component - { - public EntityUid Carried = default!; - } -} diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs b/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs deleted file mode 100644 index 0faff7d8078..00000000000 --- a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System.Numerics; -using System.Threading; -using Content.Server.DoAfter; -using Content.Server.Body.Systems; -using Content.Server.Hands.Systems; -using Content.Server.Resist; -using Content.Server.Popups; -using Content.Server.Inventory; -using Content.Server.Nyanotrasen.Item.PseudoItem; -using Content.Shared.Climbing; // Shared instead of Server -using Content.Shared.Mobs; -using Content.Shared.DoAfter; -using Content.Shared.Buckle.Components; -using Content.Shared.Hands.Components; -using Content.Shared.Hands; -using Content.Shared.Stunnable; -using Content.Shared.Interaction.Events; -using Content.Shared.Verbs; -using Content.Shared.Climbing.Events; // Added this. -using Content.Shared.Carrying; -using Content.Shared.Movement.Events; -using Content.Shared.Movement.Systems; -using Content.Shared.Pulling; -using Content.Shared.Standing; -using Content.Shared.ActionBlocker; -using Content.Shared.Inventory.VirtualItem; -using Content.Shared.Item; -using Content.Shared.Throwing; -using Content.Shared.Mobs.Systems; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Events; -using Content.Shared.Movement.Pulling.Systems; -using Content.Shared.Nyanotrasen.Item.PseudoItem; -using Content.Shared.Storage; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Components; - -namespace Content.Server.Carrying -{ - public sealed class CarryingSystem : EntitySystem - { - [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!; - [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly StandingStateSystem _standingState = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - [Dependency] private readonly PullingSystem _pullingSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly EscapeInventorySystem _escapeInventorySystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; - [Dependency] private readonly RespiratorSystem _respirator = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly PseudoItemSystem _pseudoItem = default!; // Needed for fitting check - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent>(AddCarryVerb); - SubscribeLocalEvent>(AddInsertCarriedVerb); - SubscribeLocalEvent(OnVirtualItemDeleted); - SubscribeLocalEvent(OnThrow); - SubscribeLocalEvent(OnParentChanged); - SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnInteractionAttempt); - SubscribeLocalEvent(OnMoveInput); - SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(OnStandAttempt); - SubscribeLocalEvent(OnInteractedWith); - SubscribeLocalEvent(OnPullAttempt); - SubscribeLocalEvent(OnStartClimb); - SubscribeLocalEvent(OnBuckleChange); - SubscribeLocalEvent(OnBuckleChange); - SubscribeLocalEvent(OnBuckleChange); - SubscribeLocalEvent(OnBuckleChange); - SubscribeLocalEvent(OnDoAfter); - } - - private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - if (!CanCarry(args.User, uid, component)) - return; - - if (HasComp(args.User)) // yeah not dealing with that - return; - - if (HasComp(args.User) || HasComp(args.Target)) - return; - - if (!_mobStateSystem.IsAlive(args.User)) - return; - - if (args.User == args.Target) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - StartCarryDoAfter(args.User, uid, component); - }, - Text = Loc.GetString("carry-verb"), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, GetVerbsEvent args) - { - // If the person is carrying someone, and the carried person is a pseudo-item, and the target entity is a storage, - // then add an action to insert the carried entity into the target - var toInsert = args.Using; - if (toInsert is not { Valid: true } || !args.CanAccess || !TryComp(toInsert, out var pseudoItem)) - return; - - if (!TryComp(args.Target, out var storageComp)) - return; - - if (!_pseudoItem.CheckItemFits((toInsert.Value, pseudoItem), (args.Target, storageComp))) - return; - - InnateVerb verb = new() - { - Act = () => - { - DropCarried(uid, toInsert.Value); - _pseudoItem.TryInsert(args.Target, toInsert.Value, pseudoItem, storageComp); - }, - Text = Loc.GetString("action-name-insert-other", ("target", toInsert)), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - /// - /// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them. - /// - private void OnVirtualItemDeleted(EntityUid uid, CarryingComponent component, VirtualItemDeletedEvent args) - { - if (!HasComp(args.BlockingEntity)) - return; - - DropCarried(uid, args.BlockingEntity); - } - - /// - /// Basically using virtual item passthrough to throw the carried person. A new age! - /// Maybe other things besides throwing should use virt items like this... - /// - private void OnThrow(EntityUid uid, CarryingComponent component, BeforeThrowEvent args) - { - if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity)) - return; - - args.ItemUid = virtItem.BlockingEntity; - - var multiplier = MassContest(uid, virtItem.BlockingEntity); - args.ThrowSpeed = 5f * multiplier; - } - - private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args) - { - var xform = Transform(uid); - if (xform.MapUid != args.OldMapId) - return; - - // Do not drop the carried entity if the new parent is a grid - if (xform.ParentUid == xform.GridUid) - return; - - DropCarried(uid, component.Carried); - } - - private void OnMobStateChanged(EntityUid uid, CarryingComponent component, MobStateChangedEvent args) - { - DropCarried(uid, component.Carried); - } - - /// - /// Only let the person being carried interact with their carrier and things on their person. - /// - private void OnInteractionAttempt(EntityUid uid, BeingCarriedComponent component, InteractionAttemptEvent args) - { - if (args.Target == null) - return; - - var targetParent = Transform(args.Target.Value).ParentUid; - - if (args.Target.Value != component.Carrier && targetParent != component.Carrier && targetParent != uid) - args.Cancelled = true; - } - - /// - /// Try to escape via the escape inventory system. - /// - private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref MoveInputEvent args) - { - if (!TryComp(uid, out var escape)) - return; - - if (!args.HasDirectionalMovement) - return; - - if (_actionBlockerSystem.CanInteract(uid, component.Carrier)) - { - // Note: the mass contest is inverted because weaker entities are supposed to take longer to escape - _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(component.Carrier, uid)); - } - } - - private void OnMoveAttempt(EntityUid uid, BeingCarriedComponent component, UpdateCanMoveEvent args) - { - args.Cancel(); - } - - private void OnStandAttempt(EntityUid uid, BeingCarriedComponent component, StandAttemptEvent args) - { - args.Cancel(); - } - - private void OnInteractedWith(EntityUid uid, BeingCarriedComponent component, GettingInteractedWithAttemptEvent args) - { - if (args.Uid != component.Carrier) - args.Cancelled = true; - } - - private void OnPullAttempt(EntityUid uid, BeingCarriedComponent component, PullAttemptEvent args) - { - args.Cancelled = true; - } - - private void OnStartClimb(EntityUid uid, BeingCarriedComponent component, ref StartClimbEvent args) - { - DropCarried(component.Carrier, uid); - } - - private void OnBuckleChange(EntityUid uid, BeingCarriedComponent component, TEvent args) // Augh - { - DropCarried(component.Carrier, uid); - } - - private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfterEvent args) - { - component.CancelToken = null; - if (args.Handled || args.Cancelled) - return; - - if (!CanCarry(args.Args.User, uid, component)) - return; - - Carry(args.Args.User, uid); - args.Handled = true; - } - private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component) - { - TimeSpan length = GetPickupDuration(carrier, carried); - - if (length >= TimeSpan.FromSeconds(9)) - { - _popupSystem.PopupEntity(Loc.GetString("carry-too-heavy"), carried, carrier, Shared.Popups.PopupType.SmallCaution); - return; - } - - if (!HasComp(carried)) - length *= 2f; - - component.CancelToken = new CancellationTokenSource(); - - var ev = new CarryDoAfterEvent(); - var args = new DoAfterArgs(EntityManager, carrier, length, ev, carried, target: carried) - { - BreakOnMove = true, - NeedHand = true - }; - - _doAfterSystem.TryStartDoAfter(args); - - // Show a popup to the person getting picked up - _popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried); - } - - private void Carry(EntityUid carrier, EntityUid carried) - { - if (TryComp(carried, out var pullable)) - _pullingSystem.TryStopPull(carried, pullable); - - var carrierXform = Transform(carrier); - var xform = Transform(carried); - _transform.AttachToGridOrMap(carrier, carrierXform); - _transform.AttachToGridOrMap(carried, xform); - xform.Coordinates = carrierXform.Coordinates; - _transform.SetParent(carried, xform, carrier, carrierXform); - - _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier); - _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier); - var carryingComp = EnsureComp(carrier); - ApplyCarrySlowdown(carrier, carried); - var carriedComp = EnsureComp(carried); - EnsureComp(carried); - - carryingComp.Carried = carried; - carriedComp.Carrier = carrier; - - _actionBlockerSystem.UpdateCanMove(carried); - } - - public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? carriedComp = null) - { - if (!Resolve(toCarry, ref carriedComp, false)) - return false; - - if (!CanCarry(carrier, toCarry, carriedComp)) - return false; - - // The second one means that carrier is a pseudo-item and is inside a bag. - if (HasComp(carrier) || HasComp(carrier)) - return false; - - if (GetPickupDuration(carrier, toCarry) > TimeSpan.FromSeconds(9)) - return false; - - Carry(carrier, toCarry); - - return true; - } - - public void DropCarried(EntityUid carrier, EntityUid carried) - { - RemComp(carrier); // get rid of this first so we don't recusrively fire that event - RemComp(carrier); - RemComp(carried); - RemComp(carried); - _actionBlockerSystem.UpdateCanMove(carried); - _virtualItemSystem.DeleteInHandsMatching(carrier, carried); - Transform(carried).AttachToGridOrMap(); - _standingState.Stand(carried); - _movementSpeed.RefreshMovementSpeedModifiers(carrier); - } - - private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried) - { - var massRatio = MassContest(carrier, carried); - - if (massRatio == 0) - massRatio = 1; - - var massRatioSq = Math.Pow(massRatio, 2); - var modifier = (1 - (0.15 / massRatioSq)); - modifier = Math.Max(0.1, modifier); - var slowdownComp = EnsureComp(carrier); - _slowdown.SetModifier(carrier, (float) modifier, (float) modifier, slowdownComp); - } - - public bool CanCarry(EntityUid carrier, EntityUid carried, CarriableComponent? carriedComp = null) - { - if (!Resolve(carried, ref carriedComp, false)) - return false; - - if (carriedComp.CancelToken != null) - return false; - - if (!HasComp(Transform(carrier).ParentUid)) - return false; - - if (HasComp(carrier) || HasComp(carried)) - return false; - - // if (_respirator.IsReceivingCPR(carried)) - // return false; - - if (!TryComp(carrier, out var hands)) - return false; - - if (hands.CountFreeHands() < carriedComp.FreeHandsRequired) - return false; - - return true; - } - - private float MassContest(EntityUid roller, EntityUid target, PhysicsComponent? rollerPhysics = null, PhysicsComponent? targetPhysics = null) - { - if (!Resolve(roller, ref rollerPhysics, false) || !Resolve(target, ref targetPhysics, false)) - return 1f; - - if (targetPhysics.FixturesMass == 0) - return 1f; - - return rollerPhysics.FixturesMass / targetPhysics.FixturesMass; - } - - private TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried) - { - var length = TimeSpan.FromSeconds(3); - - var mod = MassContest(carrier, carried); - if (mod != 0) - length /= mod; - - return length; - } - - public override void Update(float frameTime) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var carried, out var comp)) - { - var carrier = comp.Carrier; - if (carrier is not { Valid: true } || carried is not { Valid: true }) - continue; - - // SOMETIMES - when an entity is inserted into disposals, or a cryosleep chamber - it can get re-parented without a proper reparent event - // when this happens, it needs to be dropped because it leads to weird behavior - if (Transform(carried).ParentUid != carrier) - { - DropCarried(carrier, carried); - continue; - } - - // Make sure the carried entity is always centered relative to the carrier, as gravity pulls can offset it otherwise - var xform = Transform(carried); - if (!xform.LocalPosition.Equals(Vector2.Zero)) - { - xform.LocalPosition = Vector2.Zero; - } - } - query.Dispose(); - } - } -} diff --git a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs index 6df387e6ba8..7437d293da6 100644 --- a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs +++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs @@ -1,9 +1,9 @@ -using Content.Server.Carrying; using Content.Server.DoAfter; using Content.Server.Item; using Content.Server.Popups; using Content.Server.Storage.EntitySystems; using Content.Shared.Bed.Sleep; +using Content.Shared.DeltaV.Carrying; using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Item; diff --git a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs index d77bec6b1ae..faf27484ce9 100644 --- a/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs +++ b/Content.Server/Nyanotrasen/Kitchen/Components/DeepFryerComponent.cs @@ -11,230 +11,184 @@ using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Server.Nyanotrasen.Kitchen.Components +namespace Content.Server.Nyanotrasen.Kitchen.Components; + +// TODO: move to shared and get rid of SharedDeepFryerComponent +[RegisterComponent, Access(typeof(SharedDeepfryerSystem))] +public sealed partial class DeepFryerComponent : SharedDeepFryerComponent { - [RegisterComponent] - [Access(typeof(SharedDeepfryerSystem))] - // This line appears to be depracted: [ComponentReference(typeof(SharedDeepFryerComponent))] - public sealed partial class DeepFryerComponent : SharedDeepFryerComponent - { - // There are three levels to how the deep fryer treats entities. - // - // 1. An entity can be rejected by the blacklist and be untouched by - // anything other than heat damage. - // - // 2. An entity can be deep-fried but not turned into an edible. The - // change will be mostly cosmetic. Any entity that does not match - // the blacklist will fall into this category. - // - // 3. An entity can be deep-fried and turned into something edible. The - // change will permit the item to be permanently destroyed by eating - // it. - - /// - /// When will the deep fryer layer on the next stage of crispiness? - /// - [DataField("nextFryTime", customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan NextFryTime { get; set; } - - /// - /// How much waste needs to be added at the next update interval? - /// - public FixedPoint2 WasteToAdd { get; set; } = FixedPoint2.Zero; - - /// - /// How often are items in the deep fryer fried? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("fryInterval")] - public TimeSpan FryInterval { get; set; } = TimeSpan.FromSeconds(5); - - /// - /// What entities cannot be deep-fried no matter what? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("blacklist")] - public EntityWhitelist? Blacklist { get; set; } - - /// - /// What entities can be deep-fried into being edible? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("whitelist")] - public EntityWhitelist? Whitelist { get; set; } - - /// - /// What are over-cooked and burned entities turned into? - /// - /// - /// To prevent unwanted destruction of items, only food can be turned - /// into this. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("charredPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? CharredPrototype { get; set; } - - /// - /// What reagents are considered valid cooking oils? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("fryingOils", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet FryingOils { get; set; } = new(); - - /// - /// What reagents are added to tasty deep-fried food? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("goodReagents")] - public List GoodReagents { get; set; } = new(); - - /// - /// What reagents are added to terrible deep-fried food? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("badReagents")] - public List BadReagents { get; set; } = new(); - - /// - /// What reagents replace every 1 unit of oil spent on frying? - /// JJ Comment: I removed Solution from this. Unsure if I need to replace it with something. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("wasteReagents")] - public List WasteReagents { get; set; } = new(); - - /// - /// What flavors go well with deep frying? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("goodFlavors", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet GoodFlavors { get; set; } = new(); - - /// - /// What flavors don't go well with deep frying? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("badFlavors", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet BadFlavors { get; set; } = new(); - - /// - /// How much is the price coefficiency of a food changed for each good flavor? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("goodFlavorPriceBonus")] - public float GoodFlavorPriceBonus { get; set; } = 0.2f; - - /// - /// How much is the price coefficiency of a food changed for each bad flavor? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("badFlavorPriceMalus")] - public float BadFlavorPriceMalus { get; set; } = -0.3f; - - /// - /// What is the name of the solution container for the fryer's oil? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("solution")] - public string SolutionName { get; set; } = "vat_oil"; - - public Solution Solution { get; set; } = default!; - - /// - /// What is the name of the entity container for items inside the deep fryer? - /// - [DataField("storage")] - public string StorageName { get; set; } = "vat_entities"; - - public BaseContainer Storage { get; set; } = default!; - - /// - /// How much solution should be imparted based on an item's size? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("solutionSizeCoefficient")] - public FixedPoint2 SolutionSizeCoefficient { get; set; } = 1f; - - /// - /// What's the maximum amount of solution that should ever be imparted? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("solutionSplitMax")] - public FixedPoint2 SolutionSplitMax { get; set; } = 10f; - - /// - /// What percent of the fryer's solution has to be oil in order for it to fry? - /// - /// - /// The chef will have to clean it out occasionally, and if too much - /// non-oil reagents are added, the vat will have to be drained. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("fryingOilThreshold")] - public FixedPoint2 FryingOilThreshold { get; set; } = 0.5f; - - /// - /// What is the bare minimum number of oil units to prevent the fryer - /// from unsafe operation? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("safeOilVolume")] - public FixedPoint2 SafeOilVolume { get; set; } = 10f; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("unsafeOilVolumeEffects")] - public List UnsafeOilVolumeEffects = new(); - - /// - /// What is the temperature of the vat when the deep fryer is powered? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("poweredTemperature")] - public float PoweredTemperature = 550.0f; - - /// - /// How many entities can this deep fryer hold? - /// - [ViewVariables(VVAccess.ReadWrite)] - public int StorageMaxEntities = 4; - - /// - /// How many entities can be held, at a minimum? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("baseStorageMaxEntities")] - public int BaseStorageMaxEntities = 4; - - // /// - // /// What upgradeable machine part dictates the quality of the storage size? - // /// - // [DataField("machinePartStorageMax", customTypeSerializer: typeof(PrototypeIdSerializer))] - // public string MachinePartStorageMax = "MatterBin"; - - /// - /// How much extra storage is added per part rating? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("storagePerPartRating")] - public int StoragePerPartRating = 4; - - /// - /// What sound is played when an item is inserted into hot oil? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("soundInsertItem")] - public SoundSpecifier SoundInsertItem = new SoundPathSpecifier("/Audio/Nyanotrasen/Machines/deepfryer_basket_add_item.ogg"); - - /// - /// What sound is played when an item is removed? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("soundRemoveItem")] - public SoundSpecifier SoundRemoveItem = new SoundPathSpecifier("/Audio/Nyanotrasen/Machines/deepfryer_basket_remove_item.ogg"); - } + // There are three levels to how the deep fryer treats entities. + // + // 1. An entity can be rejected by the blacklist and be untouched by + // anything other than heat damage. + // + // 2. An entity can be deep-fried but not turned into an edible. The + // change will be mostly cosmetic. Any entity that does not match + // the blacklist will fall into this category. + // + // 3. An entity can be deep-fried and turned into something edible. The + // change will permit the item to be permanently destroyed by eating + // it. + + /// + /// When will the deep fryer layer on the next stage of crispiness? + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextFryTime; + + /// + /// How much waste needs to be added at the next update interval? + /// + [DataField] + public FixedPoint2 WasteToAdd = FixedPoint2.Zero; + + /// + /// How often are items in the deep fryer fried? + /// + [DataField] + public TimeSpan FryInterval = TimeSpan.FromSeconds(5); + + /// + /// What entities cannot be deep-fried no matter what? + /// + [DataField] + public EntityWhitelist? Blacklist; + + /// + /// What entities can be deep-fried into being edible? + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// What are over-cooked and burned entities turned into? + /// + /// + /// To prevent unwanted destruction of items, only food can be turned + /// into this. + /// + [DataField] + public EntProtoId? CharredPrototype; + + /// + /// What reagents are considered valid cooking oils? + /// + [DataField] + public HashSet> FryingOils = new(); + + /// + /// What reagents are added to tasty deep-fried food? + /// + [DataField] + public List GoodReagents = new(); + + /// + /// What reagents are added to terrible deep-fried food? + /// + [DataField] + public List BadReagents = new(); + + /// + /// What reagents replace every 1 unit of oil spent on frying? + /// + [DataField] + public List WasteReagents = new(); + + /// + /// What flavors go well with deep frying? + /// + [DataField] + public HashSet> GoodFlavors = new(); + + /// + /// What flavors don't go well with deep frying? + /// + [DataField] + public HashSet> BadFlavors = new(); + + /// + /// How much is the price coefficiency of a food changed for each good flavor? + /// + [DataField] + public float GoodFlavorPriceBonus = 0.2f; + + /// + /// How much is the price coefficiency of a food changed for each bad flavor? + /// + [DataField] + public float BadFlavorPriceMalus = -0.3f; + + /// + /// What is the name of the solution container for the fryer's oil? + /// + [DataField] + public string SolutionName = "vat_oil"; + + // TODO: Entity + public Solution Solution = default!; + + /// + /// What is the name of the entity container for items inside the deep fryer? + /// + [DataField("storage")] + public string StorageName = "vat_entities"; + + public BaseContainer Storage = default!; + + /// + /// How much solution should be imparted based on an item's size? + /// + [DataField] + public FixedPoint2 SolutionSizeCoefficient = 1f; + + /// + /// What's the maximum amount of solution that should ever be imparted? + /// + [DataField] + public FixedPoint2 SolutionSplitMax = 10f; + + /// + /// What percent of the fryer's solution has to be oil in order for it to fry? + /// + /// + /// The chef will have to clean it out occasionally, and if too much + /// non-oil reagents are added, the vat will have to be drained. + /// + [DataField] + public FixedPoint2 FryingOilThreshold = 0.5f; + + /// + /// What is the bare minimum number of oil units to prevent the fryer + /// from unsafe operation? + /// + [DataField] + public FixedPoint2 SafeOilVolume = 10f; + + [DataField] + public List UnsafeOilVolumeEffects = new(); + + /// + /// What is the temperature of the vat when the deep fryer is powered? + /// + [DataField] + public float PoweredTemperature = 550.0f; + + /// + /// How many entities can this deep fryer hold? + /// + [DataField] + public int StorageMaxEntities = 4; + + /// + /// What sound is played when an item is inserted into hot oil? + /// + [DataField] + public SoundSpecifier SoundInsertItem = new SoundPathSpecifier("/Audio/Nyanotrasen/Machines/deepfryer_basket_add_item.ogg"); + + /// + /// What sound is played when an item is removed? + /// + [DataField] + public SoundSpecifier SoundRemoveItem = new SoundPathSpecifier("/Audio/Nyanotrasen/Machines/deepfryer_basket_remove_item.ogg"); } diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs index 3f93787934c..fa2807509a6 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Results.cs @@ -11,15 +11,20 @@ using Content.Shared.FixedPoint; using Content.Shared.Mobs.Components; using Content.Shared.NPC; +using Content.Shared.Nutrition; using Content.Shared.Nutrition.Components; using Content.Shared.Nyanotrasen.Kitchen.Components; using Content.Shared.Paper; +using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Nyanotrasen.Kitchen.EntitySystems; public sealed partial class DeepFryerSystem { + private HashSet> _badFlavors = new(); + private HashSet> _goodFlavors = new(); + /// /// Make an item look deep-fried. /// @@ -129,33 +134,33 @@ private void MakeEdible(EntityUid uid, DeepFryerComponent component, EntityUid i var extraSolution = new Solution(); if (TryComp(item, out FlavorProfileComponent? flavorProfileComponent)) { - HashSet goodFlavors = new(flavorProfileComponent.Flavors); - goodFlavors.IntersectWith(component.GoodFlavors); + _goodFlavors.Clear(); + _goodFlavors.IntersectWith(component.GoodFlavors); - HashSet badFlavors = new(flavorProfileComponent.Flavors); - badFlavors.IntersectWith(component.BadFlavors); + _badFlavors.Clear(); + _badFlavors.IntersectWith(component.BadFlavors); deepFriedComponent.PriceCoefficient = Math.Max(0.01f, 1.0f - + goodFlavors.Count * component.GoodFlavorPriceBonus - - badFlavors.Count * component.BadFlavorPriceMalus); + + _goodFlavors.Count * component.GoodFlavorPriceBonus + - _badFlavors.Count * component.BadFlavorPriceMalus); - if (goodFlavors.Count > 0) + if (_goodFlavors.Count > 0) { foreach (var reagent in component.GoodReagents) { - extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * goodFlavors.Count); + extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * _goodFlavors.Count); // Mask the taste of "medicine." flavorProfileComponent.IgnoreReagents.Add(reagent.Reagent.ToString()); } } - if (badFlavors.Count > 0) + if (_badFlavors.Count > 0) { foreach (var reagent in component.BadReagents) { - extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * badFlavors.Count); + extraSolution.AddReagent(reagent.Reagent.ToString(), reagent.Quantity * _badFlavors.Count); } } } diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs index 40a658f0c44..4ac8684a7a3 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs @@ -105,7 +105,6 @@ public override void Initialize() SubscribeLocalEvent(OnInitDeepFryer); SubscribeLocalEvent(OnPowerChange); - // SubscribeLocalEvent(OnRefreshParts); SubscribeLocalEvent(OnDeconstruct); SubscribeLocalEvent(OnDestruction); SubscribeLocalEvent(OnThrowHitBy); @@ -437,14 +436,6 @@ private void OnDestruction(EntityUid uid, DeepFryerComponent component, Destruct _containerSystem.EmptyContainer(component.Storage, true); } - // private void OnRefreshParts(EntityUid uid, DeepFryerComponent component, RefreshPartsEvent args) - // { - // var ratingStorage = args.PartRatings[component.MachinePartStorageMax]; - // - // component.StorageMaxEntities = component.BaseStorageMaxEntities + - // (int) (component.StoragePerPartRating * (ratingStorage - 1)); - // } - /// /// Allow thrown items to land in a basket. /// diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs index 93a83465861..eec8ebb5072 100644 --- a/Content.Server/Resist/EscapeInventorySystem.cs +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -1,6 +1,5 @@ using Content.Server.Popups; using Content.Shared.Storage; -using Content.Server.Carrying; // Carrying system from Nyanotrasen. using Content.Shared.Inventory; using Content.Shared.Hands.EntitySystems; using Content.Shared.Storage.Components; @@ -25,7 +24,6 @@ public sealed class EscapeInventorySystem : EntitySystem [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly CarryingSystem _carryingSystem = default!; // Carrying system from Nyanotrasen. [Dependency] private readonly SharedActionsSystem _actions = default!; // DeltaV /// @@ -109,13 +107,6 @@ private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, Esca if (args.Handled || args.Cancelled) return; - if (TryComp(uid, out var carried)) // Start of carrying system of nyanotrasen. - { - _carryingSystem.DropCarried(carried.Carrier, uid); - return; - } // End of carrying system of nyanotrasen. - - _containerSystem.AttachParentToContainerOrGrid((uid, Transform(uid))); args.Handled = true; } diff --git a/Content.Shared/Cargo/CargoBountyHistoryData.cs b/Content.Shared/Cargo/CargoBountyHistoryData.cs new file mode 100644 index 00000000000..43da42d5587 --- /dev/null +++ b/Content.Shared/Cargo/CargoBountyHistoryData.cs @@ -0,0 +1,46 @@ +using Robust.Shared.Serialization; +using Content.Shared.Cargo.Prototypes; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Cargo; + +/// +/// A data structure for storing historical information about bounties. +/// +[DataDefinition, NetSerializable, Serializable] +public readonly partial record struct CargoBountyHistoryData +{ + /// + /// A unique id used to identify the bounty + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public string Id { get; init; } = string.Empty; + + /// + /// Optional name of the actor that skipped the bounty. + /// Only set when the bounty has been skipped. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public string? ActorName { get; init; } = default; + + /// + /// Time when this bounty was completed or skipped + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan Timestamp { get; init; } = TimeSpan.MinValue; + + /// + /// The prototype containing information about the bounty. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] + public ProtoId Bounty { get; init; } = string.Empty; + + public CargoBountyHistoryData(CargoBountyData bounty, TimeSpan timestamp, string? actorName) + { + Bounty = bounty.Bounty; + Id = bounty.Id; + ActorName = actorName; + Timestamp = timestamp; + } +} diff --git a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs index bf82a08127e..8c78312be19 100644 --- a/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs +++ b/Content.Shared/Cargo/Components/CargoBountyConsoleComponent.cs @@ -50,11 +50,13 @@ public sealed partial class CargoBountyConsoleComponent : Component public sealed class CargoBountyConsoleState : BoundUserInterfaceState { public List Bounties; + public List History; public TimeSpan UntilNextSkip; - public CargoBountyConsoleState(List bounties, TimeSpan untilNextSkip) + public CargoBountyConsoleState(List bounties, List history, TimeSpan untilNextSkip) { Bounties = bounties; + History = history; UntilNextSkip = untilNextSkip; } } diff --git a/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs b/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs new file mode 100644 index 00000000000..7e519e7e04b --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Carrying; + +/// +/// Stores the carrier of an entity being carried. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))] +[AutoGenerateComponentState] +public sealed partial class BeingCarriedComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid Carrier; +} diff --git a/Content.Shared/DeltaV/Carrying/CarriableComponent.cs b/Content.Shared/DeltaV/Carrying/CarriableComponent.cs new file mode 100644 index 00000000000..ad1968aec62 --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarriableComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Carrying; + +[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))] +public sealed partial class CarriableComponent : Component +{ + /// + /// Number of free hands required + /// to carry the entity + /// + [DataField] + public int FreeHandsRequired = 2; +} diff --git a/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs b/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs new file mode 100644 index 00000000000..7ea0375518a --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.DeltaV.Carrying; + +[Serializable, NetSerializable] +public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/DeltaV/Carrying/CarryingComponent.cs b/Content.Shared/DeltaV/Carrying/CarryingComponent.cs new file mode 100644 index 00000000000..e6661da0e04 --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarryingComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Carrying; + +/// +/// Added to an entity when they are carrying somebody. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))] +[AutoGenerateComponentState] +public sealed partial class CarryingComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid Carried; +} diff --git a/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs new file mode 100644 index 00000000000..9e1be89370c --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Carrying; + +[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSlowdownSystem))] +[AutoGenerateComponentState] +public sealed partial class CarryingSlowdownComponent : Component +{ + /// + /// Modifier for both walk and sprint speed. + /// + [DataField, AutoNetworkedField] + public float Modifier = 1.0f; +} diff --git a/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs new file mode 100644 index 00000000000..677c53eedc0 --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Movement.Systems; + +namespace Content.Shared.DeltaV.Carrying; + +public sealed class CarryingSlowdownSystem : EntitySystem +{ + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRefreshMoveSpeed); + } + + public void SetModifier(Entity ent, float modifier) + { + ent.Comp ??= EnsureComp(ent); + ent.Comp.Modifier = modifier; + Dirty(ent, ent.Comp); + + _movementSpeed.RefreshMovementSpeedModifiers(ent); + } + + private void OnRefreshMoveSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + args.ModifySpeed(ent.Comp.Modifier, ent.Comp.Modifier); + } +} diff --git a/Content.Shared/DeltaV/Carrying/CarryingSystem.cs b/Content.Shared/DeltaV/Carrying/CarryingSystem.cs new file mode 100644 index 00000000000..2b47c49abd1 --- /dev/null +++ b/Content.Shared/DeltaV/Carrying/CarryingSystem.cs @@ -0,0 +1,384 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Buckle.Components; +using Content.Shared.Climbing.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory.VirtualItem; +using Content.Shared.Item; +using Content.Shared.Mobs; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Nyanotrasen.Item.PseudoItem; +using Content.Shared.Popups; +using Content.Shared.Pulling; +using Content.Shared.Resist; +using Content.Shared.Standing; +using Content.Shared.Storage; +using Content.Shared.Stunnable; +using Content.Shared.Throwing; +using Content.Shared.Verbs; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using System.Numerics; + +namespace Content.Shared.DeltaV.Carrying; + +public sealed class CarryingSystem : EntitySystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; + [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPseudoItemSystem _pseudoItem = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly StandingStateSystem _standingState = default!; + [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!; + + private EntityQuery _physicsQuery; + + public override void Initialize() + { + base.Initialize(); + + _physicsQuery = GetEntityQuery(); + + SubscribeLocalEvent>(AddCarryVerb); + SubscribeLocalEvent>(AddInsertCarriedVerb); + SubscribeLocalEvent(OnVirtualItemDeleted); + SubscribeLocalEvent(OnThrow); + SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnInteractionAttempt); + SubscribeLocalEvent(OnMoveAttempt); + SubscribeLocalEvent(OnStandAttempt); + SubscribeLocalEvent(OnInteractedWith); + SubscribeLocalEvent(OnPullAttempt); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDrop); + SubscribeLocalEvent(OnDoAfter); + } + + private void AddCarryVerb(Entity ent, ref GetVerbsEvent args) + { + var user = args.User; + var target = args.Target; + if (!args.CanInteract || !args.CanAccess || user == target) + return; + + if (!CanCarry(user, ent)) + return; + + args.Verbs.Add(new AlternativeVerb() + { + Act = () => StartCarryDoAfter(user, ent), + Text = Loc.GetString("carry-verb"), + Priority = 2 + }); + } + + private void AddInsertCarriedVerb(Entity ent, ref GetVerbsEvent args) + { + // If the person is carrying someone, and the carried person is a pseudo-item, and the target entity is a storage, + // then add an action to insert the carried entity into the target + // AKA put carried felenid into a duffelbag + if (args.Using is not {} carried || !args.CanAccess || !TryComp(carried, out var pseudoItem)) + return; + + var target = args.Target; + if (!TryComp(target, out var storageComp)) + return; + + if (!_pseudoItem.CheckItemFits((carried, pseudoItem), (target, storageComp))) + return; + + args.Verbs.Add(new InnateVerb() + { + Act = () => + { + DropCarried(ent, carried); + _pseudoItem.TryInsert(target, carried, pseudoItem, storageComp); + }, + Text = Loc.GetString("action-name-insert-other", ("target", carried)), + Priority = 2 + }); + } + + /// + /// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them. + /// + private void OnVirtualItemDeleted(Entity ent, ref VirtualItemDeletedEvent args) + { + if (HasComp(args.BlockingEntity)) + DropCarried(ent, args.BlockingEntity); + } + + /// + /// Basically using virtual item passthrough to throw the carried person. A new age! + /// Maybe other things besides throwing should use virt items like this... + /// + private void OnThrow(Entity ent, ref BeforeThrowEvent args) + { + if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity)) + return; + + var carried = virtItem.BlockingEntity; + args.ItemUid = carried; + + args.ThrowSpeed = 5f * MassContest(ent, carried); + } + + private void OnParentChanged(Entity ent, ref EntParentChangedMessage args) + { + var xform = Transform(ent); + if (xform.MapUid != args.OldMapId) + return; + + // Do not drop the carried entity if the new parent is a grid + if (xform.ParentUid == xform.GridUid) + return; + + DropCarried(ent, ent.Comp.Carried); + } + + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + DropCarried(ent, ent.Comp.Carried); + } + + /// + /// Only let the person being carried interact with their carrier and things on their person. + /// + private void OnInteractionAttempt(Entity ent, ref InteractionAttemptEvent args) + { + if (args.Target is not {} target) + return; + + var targetParent = Transform(target).ParentUid; + + var carrier = ent.Comp.Carrier; + if (target != carrier && targetParent != carrier && targetParent != ent.Owner) + args.Cancelled = true; + } + + private void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args) + { + args.Cancel(); + } + + private void OnStandAttempt(Entity ent, ref StandAttemptEvent args) + { + args.Cancel(); + } + + private void OnInteractedWith(Entity ent, ref GettingInteractedWithAttemptEvent args) + { + if (args.Uid != ent.Comp.Carrier) + args.Cancelled = true; + } + + private void OnPullAttempt(Entity ent, ref PullAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnDrop(Entity ent, ref TEvent args) // Augh + { + DropCarried(ent.Comp.Carrier, ent); + } + + private void OnDoAfter(Entity ent, ref CarryDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + if (!CanCarry(args.Args.User, ent)) + return; + + Carry(args.Args.User, ent); + args.Handled = true; + } + + private void StartCarryDoAfter(EntityUid carrier, Entity carried) + { + TimeSpan length = GetPickupDuration(carrier, carried); + + if (length.TotalSeconds >= 9f) + { + _popup.PopupClient(Loc.GetString("carry-too-heavy"), carried, carrier, PopupType.SmallCaution); + return; + } + + if (!HasComp(carried)) + length *= 2f; + + var ev = new CarryDoAfterEvent(); + var args = new DoAfterArgs(EntityManager, carrier, length, ev, carried, target: carried) + { + BreakOnMove = true, + NeedHand = true + }; + + _doAfter.TryStartDoAfter(args); + + // Show a popup to the person getting picked up + _popup.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried); + } + + private void Carry(EntityUid carrier, EntityUid carried) + { + if (TryComp(carried, out var pullable)) + _pulling.TryStopPull(carried, pullable); + + var carrierXform = Transform(carrier); + var xform = Transform(carried); + _transform.AttachToGridOrMap(carrier, carrierXform); + _transform.AttachToGridOrMap(carried, xform); + _transform.SetParent(carried, xform, carrier, carrierXform); + + var carryingComp = EnsureComp(carrier); + carryingComp.Carried = carried; + Dirty(carrier, carryingComp); + var carriedComp = EnsureComp(carried); + carriedComp.Carrier = carrier; + Dirty(carried, carriedComp); + EnsureComp(carried); + + ApplyCarrySlowdown(carrier, carried); + + _actionBlocker.UpdateCanMove(carried); + + if (_net.IsClient) // no spawning prediction + return; + + _virtualItem.TrySpawnVirtualItemInHand(carried, carrier); + _virtualItem.TrySpawnVirtualItemInHand(carried, carrier); + } + + public bool TryCarry(EntityUid carrier, Entity toCarry) + { + if (!Resolve(toCarry, ref toCarry.Comp, false)) + return false; + + if (!CanCarry(carrier, (toCarry, toCarry.Comp))) + return false; + + // The second one means that carrier is a pseudo-item and is inside a bag. + if (HasComp(carrier) || HasComp(carrier)) + return false; + + if (GetPickupDuration(carrier, toCarry).TotalSeconds > 9f) + return false; + + Carry(carrier, toCarry); + return true; + } + + public void DropCarried(EntityUid carrier, EntityUid carried) + { + Drop(carried); + RemComp(carrier); // get rid of this first so we don't recursively fire that event + RemComp(carrier); + _virtualItem.DeleteInHandsMatching(carrier, carried); + _movementSpeed.RefreshMovementSpeedModifiers(carrier); + } + + private void Drop(EntityUid carried) + { + RemComp(carried); + RemComp(carried); // TODO SHITMED: make sure this doesnt let you make someone with no legs walk + _actionBlocker.UpdateCanMove(carried); + Transform(carried).AttachToGridOrMap(); + _standingState.Stand(carried); + } + + private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried) + { + var massRatio = MassContest(carrier, carried); + + if (massRatio == 0) + massRatio = 1; + + var massRatioSq = Math.Pow(massRatio, 2); + var modifier = (1 - (0.15 / massRatioSq)); + modifier = Math.Max(0.1, modifier); + _slowdown.SetModifier(carrier, (float) modifier); + } + + public bool CanCarry(EntityUid carrier, Entity carried) + { + return + carrier != carried.Owner && + // can't carry multiple people, even if you have 4 hands it will break invariants when removing carryingcomponent for first carried person + !HasComp(carrier) && + // can't carry someone in a locker, buckled, etc + HasComp(Transform(carrier).ParentUid) && + // no tower of spacemen or stack overflow + !HasComp(carrier) && + !HasComp(carried) && + // finally check that there are enough free hands + TryComp(carrier, out var hands) && + hands.CountFreeHands() >= carried.Comp.FreeHandsRequired; + } + + private float MassContest(EntityUid roller, EntityUid target) + { + if (!_physicsQuery.TryComp(roller, out var rollerPhysics) || !_physicsQuery.TryComp(target, out var targetPhysics)) + return 1f; + + if (targetPhysics.FixturesMass == 0) + return 1f; + + return rollerPhysics.FixturesMass / targetPhysics.FixturesMass; + } + + private TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried) + { + var length = TimeSpan.FromSeconds(3); + + var mod = MassContest(carrier, carried); + if (mod != 0) + length /= mod; + + return length; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var carried, out var comp, out var xform)) + { + var carrier = comp.Carrier; + if (TerminatingOrDeleted(carrier)) + { + RemCompDeferred(carried); + continue; + } + + // SOMETIMES - when an entity is inserted into disposals, or a cryosleep chamber - it can get re-parented without a proper reparent event + // when this happens, it needs to be dropped because it leads to weird behavior + if (xform.ParentUid != carrier) + { + DropCarried(carrier, carried); + continue; + } + + // Make sure the carried entity is always centered relative to the carrier, as gravity pulls can offset it otherwise + _transform.SetLocalPosition(carried, Vector2.Zero); + } + } +} diff --git a/Content.Shared/DeltaV/Item/ItemToggle/Components/ItemToggleExamineComponent.cs b/Content.Shared/DeltaV/Item/ItemToggle/Components/ItemToggleExamineComponent.cs new file mode 100644 index 00000000000..e8064d79456 --- /dev/null +++ b/Content.Shared/DeltaV/Item/ItemToggle/Components/ItemToggleExamineComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.DeltaV.Item.ItemToggle.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Item.ItemToggle.Components; + +/// +/// Adds examine text when the item is on or off. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(ItemToggleExamineSystem))] +public sealed partial class ItemToggleExamineComponent : Component +{ + [DataField(required: true)] + public LocId On; + + [DataField(required: true)] + public LocId Off; +} diff --git a/Content.Shared/DeltaV/Item/ItemToggle/Systems/ItemToggleExamineSystem.cs b/Content.Shared/DeltaV/Item/ItemToggle/Systems/ItemToggleExamineSystem.cs new file mode 100644 index 00000000000..5abb0aec78b --- /dev/null +++ b/Content.Shared/DeltaV/Item/ItemToggle/Systems/ItemToggleExamineSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.DeltaV.Item.ItemToggle.Components; +using Content.Shared.Examine; +using Content.Shared.Item.ItemToggle; + +namespace Content.Shared.DeltaV.Item.ItemToggle.Systems; + +public sealed class ItemToggleExamineSystem : EntitySystem +{ + [Dependency] private readonly ItemToggleSystem _toggle = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + var msg = _toggle.IsActivated(ent.Owner) ? ent.Comp.On : ent.Comp.Off; + args.PushMarkup(Loc.GetString(msg)); + } +} diff --git a/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs b/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs new file mode 100644 index 00000000000..ec2c272695b --- /dev/null +++ b/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DeltaV.Weather.Components; + +/// +/// Makes an entity not take damage from ash storms. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class AshStormImmuneComponent : Component; diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs deleted file mode 100644 index 6acd6b775f3..00000000000 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Carrying -{ - [Serializable, NetSerializable] - public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs deleted file mode 100644 index aabde66af0d..00000000000 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Carrying -{ - [RegisterComponent, NetworkedComponent, Access(typeof(CarryingSlowdownSystem))] - - public sealed partial class CarryingSlowdownComponent : Component - { - [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)] - public float WalkModifier = 1.0f; - - [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)] - public float SprintModifier = 1.0f; - } - - [Serializable, NetSerializable] - public sealed class CarryingSlowdownComponentState : ComponentState - { - public float WalkModifier; - public float SprintModifier; - public CarryingSlowdownComponentState(float walkModifier, float sprintModifier) - { - WalkModifier = walkModifier; - SprintModifier = sprintModifier; - } - } -} diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs deleted file mode 100644 index 9b9c8cec10f..00000000000 --- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Content.Shared.Movement.Systems; -using Robust.Shared.GameStates; - -namespace Content.Shared.Carrying -{ - public sealed class CarryingSlowdownSystem : EntitySystem - { - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnGetState); - SubscribeLocalEvent(OnHandleState); - SubscribeLocalEvent(OnRefreshMoveSpeed); - } - - public void SetModifier(EntityUid uid, float walkSpeedModifier, float sprintSpeedModifier, CarryingSlowdownComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - component.WalkModifier = walkSpeedModifier; - component.SprintModifier = sprintSpeedModifier; - _movementSpeed.RefreshMovementSpeedModifiers(uid); - } - private void OnGetState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentGetState args) - { - args.State = new CarryingSlowdownComponentState(component.WalkModifier, component.SprintModifier); - } - - private void OnHandleState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentHandleState args) - { - if (args.Current is CarryingSlowdownComponentState state) - { - component.WalkModifier = state.WalkModifier; - component.SprintModifier = state.SprintModifier; - - _movementSpeed.RefreshMovementSpeedModifiers(uid); - } - } - private void OnRefreshMoveSpeed(EntityUid uid, CarryingSlowdownComponent component, RefreshMovementSpeedModifiersEvent args) - { - args.ModifySpeed(component.WalkModifier, component.SprintModifier); - } - } -} diff --git a/Content.Shared/Stacks/StackComponent.cs b/Content.Shared/Stacks/StackComponent.cs index 7137f8c0c22..b18f9b0d051 100644 --- a/Content.Shared/Stacks/StackComponent.cs +++ b/Content.Shared/Stacks/StackComponent.cs @@ -24,7 +24,7 @@ public sealed partial class StackComponent : Component /// [ViewVariables(VVAccess.ReadOnly)] [DataField("maxCountOverride")] - public int? MaxCountOverride { get; set; } + public int? MaxCountOverride { get; set; } /// /// Set to true to not reduce the count when used. @@ -78,6 +78,14 @@ public sealed partial class StackComponent : Component [DataField("layerStates")] [ViewVariables(VVAccess.ReadWrite)] public List LayerStates = new(); + + // Frontier: transforming Amount, MaxCount in speso stacks + /// + /// An optional function to adjust the layers used for a stack's appearance. + /// + [DataField] + public StackLayerFunction LayerFunction = StackLayerFunction.None; + // End Frontier } [Serializable, NetSerializable] diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs index 7a8961485d6..5cc24992d31 100644 --- a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs +++ b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Storage.Components; using Content.Shared.Inventory; +using Content.Shared.Item.ItemToggle; // DeltaV using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Physics.Components; @@ -15,6 +16,7 @@ public sealed class MagnetPickupSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly ItemToggleSystem _toggle = default!; // DeltaV [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; @@ -49,11 +51,18 @@ public override void Update(float frameTime) comp.NextScan += ScanDelay; - if (!_inventory.TryGetContainingSlot((uid, xform, meta), out var slotDef)) + // Begin DeltaV Addition: Make ore bags use ItemToggle + if (!_toggle.IsActivated(uid)) continue; + // End DeltaV Addition - if ((slotDef.SlotFlags & comp.SlotFlags) == 0x0) - continue; + // Begin DeltaV Removals: Allow ore bags to work inhand + //if (!_inventory.TryGetContainingSlot((uid, xform, meta), out var slotDef)) + // continue; + + //if ((slotDef.SlotFlags & comp.SlotFlags) == 0x0) + // continue; + // End DeltaV Removals // No space if (!_storage.HasSpace((uid, storage))) diff --git a/Content.Shared/Weather/SharedWeatherSystem.cs b/Content.Shared/Weather/SharedWeatherSystem.cs index acd43055388..95c089fa10e 100644 --- a/Content.Shared/Weather/SharedWeatherSystem.cs +++ b/Content.Shared/Weather/SharedWeatherSystem.cs @@ -36,6 +36,7 @@ private void OnWeatherUnpaused(EntityUid uid, WeatherComponent component, ref En if (weather.EndTime != null) weather.EndTime = weather.EndTime.Value + args.PausedTime; } + component.NextUpdate += args.PausedTime; // DeltaV } public bool CanWeatherAffect(EntityUid uid, MapGridComponent grid, TileRef tileRef) diff --git a/Content.Shared/Weather/WeatherComponent.cs b/Content.Shared/Weather/WeatherComponent.cs index eaf901fb424..e16fe978fa6 100644 --- a/Content.Shared/Weather/WeatherComponent.cs +++ b/Content.Shared/Weather/WeatherComponent.cs @@ -14,6 +14,18 @@ public sealed partial class WeatherComponent : Component [DataField] public Dictionary, WeatherData> Weather = new(); + /// + /// DeltaV: How long to wait between updating weather effects. + /// + [DataField] + public TimeSpan UpdateDelay = TimeSpan.FromSeconds(1); + + /// + /// DeltaV: When to next update weather effects (damage). + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextUpdate = TimeSpan.Zero; + public static readonly TimeSpan StartupTime = TimeSpan.FromSeconds(15); public static readonly TimeSpan ShutdownTime = TimeSpan.FromSeconds(15); } diff --git a/Content.Shared/Weather/WeatherPrototype.cs b/Content.Shared/Weather/WeatherPrototype.cs index 3803c37d4ce..af585d8d7c4 100644 --- a/Content.Shared/Weather/WeatherPrototype.cs +++ b/Content.Shared/Weather/WeatherPrototype.cs @@ -1,3 +1,5 @@ +using Content.Shared.Damage; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -20,4 +22,17 @@ public sealed partial class WeatherPrototype : IPrototype /// [ViewVariables(VVAccess.ReadWrite), DataField("sound")] public SoundSpecifier? Sound; + + /// + /// DeltaV: Damage you can take from being in this weather. + /// Only applies when weather has fully set in. + /// + [DataField] + public DamageSpecifier? Damage; + + /// + /// DeltaV: Don't damage entities that match this blacklist. + /// + [DataField] + public EntityWhitelist? DamageBlacklist; } diff --git a/Content.Shared/_NF/Stacks/Components/StackLayerThresholdComponent.cs b/Content.Shared/_NF/Stacks/Components/StackLayerThresholdComponent.cs new file mode 100644 index 00000000000..98d3bb0e61f --- /dev/null +++ b/Content.Shared/_NF/Stacks/Components/StackLayerThresholdComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Stacks.Components; + +[RegisterComponent] +public sealed partial class StackLayerThresholdComponent : Component +{ + /// + /// A list of thresholds to check against the number of things in the stack. + /// Each exceeded threshold will cause the next layer to be displayed. + /// Should be sorted in ascending order. + /// + [DataField(required: true)] + public List Thresholds = new List(); +} diff --git a/Content.Shared/_NF/Stacks/StackLayerFunction.cs b/Content.Shared/_NF/Stacks/StackLayerFunction.cs new file mode 100644 index 00000000000..c655f3f76c2 --- /dev/null +++ b/Content.Shared/_NF/Stacks/StackLayerFunction.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Stacks; + +public enum StackLayerFunction +{ + None, + Threshold +} diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 80c31975690..db32d37ed23 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -1,62 +1,4 @@ Entries: -- author: DangerRevolution - changes: - - message: Moths can now eat skirts - type: Fix - id: 283 - time: '2024-03-17T17:45:11.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/976 -- author: UnicornOnLSD - changes: - - message: pebble minor patch 3 - type: Tweak - id: 284 - time: '2024-03-18T18:35:38.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/977 -- author: DangerRevolution and Noctis - changes: - - message: Added lunchboxes to the chefvend and dinnerwarevend for the Chef to fill - or hand out pre-made! - type: Add - id: 285 - time: '2024-03-19T11:39:39.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/980 -- author: rosieposieeee - changes: - - message: Added Corpsman-only access and airlocks. - type: Add - id: 286 - time: '2024-03-19T12:13:13.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/892 -- author: rosieposieeee - changes: - - message: Submarine has received another batch of design tweaks and revisions. - The deepest recesses of the station are shifting... - type: Tweak - id: 287 - time: '2024-03-19T23:50:03.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/809 -- author: Rose - changes: - - message: Added Submarine Station back to the map pool (for real this time) - type: Add - id: 288 - time: '2024-03-20T22:49:59.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/984 -- author: rosieposieeee - changes: - - message: More fixes for Submarine! Thank you everyone for your feedback. - type: Fix - id: 289 - time: '2024-03-21T06:10:41.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/987 -- author: DangerRevolution - changes: - - message: Added lunchboxes to various staff lockers - type: Add - id: 290 - time: '2024-03-21T18:46:44.0000000+00:00' - url: https://github.com/DeltaV-Station/Delta-v/pull/989 - author: Velcroboy changes: - message: Reverted AME buff. AME will require more than 1 core again to function @@ -3818,3 +3760,63 @@ id: 782 time: '2024-12-17T12:49:40.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/2378 +- author: deltanedas + changes: + - message: Plasma and diamonds are more abundant on lavaland. + type: Tweak + id: 783 + time: '2024-12-17T18:17:41.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2441 +- author: BlitzTheSquishy & Yuukitten + changes: + - message: Brand new drink from the people that brought you Dr. Gibb, Dr Gibb Blood-Red! + In a Dr. Gibb vendor near you! + type: Add + id: 784 + time: '2024-12-18T01:12:44.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2250 +- author: Stop-Signs + changes: + - message: A new loadout is available in the fugitives stash + type: Add + id: 785 + time: '2024-12-18T01:26:19.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2374 +- author: deltanedas + changes: + - message: Ash storms can now happen on lavaland, run for shelter if you get caught + in one! + type: Add + id: 786 + time: '2024-12-18T01:27:23.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2445 +- author: paige404 + changes: + - message: Rodentia with bat snouts now look correct no matter their facing. + type: Fix + id: 787 + time: '2024-12-18T07:03:37.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2470 +- author: deltanedas + changes: + - message: Fixed not being able to throw felenids... + type: Fix + id: 788 + time: '2024-12-18T11:22:19.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2466 +- author: Hell_Cat, deltanedas + changes: + - message: Ore bag magnet can now be turned on without being worn as a belt. Mining + borgs rejoice! + type: Tweak + id: 789 + time: '2024-12-18T11:33:37.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2467 +- author: BarryNorfolk + changes: + - message: Added a history tab to the bounty computer, showing all past completed + and skipped orders (and who skipped them). + type: Add + id: 790 + time: '2024-12-18T13:36:06.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2473 diff --git a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl index 4314cbf4496..4d849c5bdab 100644 --- a/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl +++ b/Resources/Locale/en-US/cargo/cargo-bounty-console.ftl @@ -18,3 +18,10 @@ bounty-console-flavor-right = v1.4 bounty-manifest-header = [font size=14][bold]Official cargo bounty manifest[/bold] (ID#{$id})[/font] bounty-manifest-list-start = Item manifest: + +bounty-console-tab-available-label = Available +bounty-console-tab-history-label = History +bounty-console-history-notice-completed-label = {$time} - Completed +bounty-console-history-notice-skipped-label = {$time} - Skipped by {$id} +bounty-console-history-completed-label = [color=limegreen]Completed[/color] +bounty-console-history-skipped-label = [color=red]Skipped[/color] diff --git a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl index 6c93c98ed3c..51b345aa339 100644 --- a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl @@ -29,6 +29,7 @@ flavor-complex-blellow = like an impossible color flavor-complex-candy-strawberry = like strawberries flavor-complex-candy-bubblegum = like bubble gum flavor-complex-double-ice-cream = like ice cream, twice +flavor-complex-drgibbbloodred = like severe malpractice candy-flavor-profile = This one is supposed to taste {$flavor}. candy-flavor-profile-multiple = This one is supposed to taste {$flavors} and {$lastFlavor}. diff --git a/Resources/Locale/en-US/deltav/fugitive/sets.ftl b/Resources/Locale/en-US/deltav/fugitive/sets.ftl index bc1361dc352..e353fd38de7 100644 --- a/Resources/Locale/en-US/deltav/fugitive/sets.ftl +++ b/Resources/Locale/en-US/deltav/fugitive/sets.ftl @@ -22,3 +22,8 @@ fugitive-set-infiltrator-name = infiltrator's kit fugitive-set-infiltrator-description = Use an Agent ID to steal access from others and go anywhere. Your freedom implanter can be used as a plan B if all else fails. + +fugitive-set-disruptor-name = disruptor's kit +fugitive-set-disruptor-description = + Hack the stations various systems and use them to your advantage. + Comes with a cryptographic sequencer and camera bug. diff --git a/Resources/Locale/en-US/deltav/reagents/meta/consumable/drink/soda.ftl b/Resources/Locale/en-US/deltav/reagents/meta/consumable/drink/soda.ftl new file mode 100644 index 00000000000..156f8aaa73a --- /dev/null +++ b/Resources/Locale/en-US/deltav/reagents/meta/consumable/drink/soda.ftl @@ -0,0 +1,2 @@ +reagent-name-dr-gibb-blood-red = Dr. Gibb Blood Red +reagent-desc-dr-gibb-blood-red = A drink to quench YOUR blood thirst. diff --git a/Resources/Locale/en-US/deltav/weather/ashstorm.ftl b/Resources/Locale/en-US/deltav/weather/ashstorm.ftl new file mode 100644 index 00000000000..49778522502 --- /dev/null +++ b/Resources/Locale/en-US/deltav/weather/ashstorm.ftl @@ -0,0 +1,3 @@ +ash-storm-telegraph = [color=red][bold]An eerie moan rises on the wind. Sheets of burning ash blacken the horizon. Seek shelter.[/bold][/color] +ash-storm-alert = [color=red][bolditalic]Smoldering clouds of scorching ash billow down around you! Get inside![/bolditalic][/color] +ash-storm-clearing = [color=red]The shrieking wind whips away the last of the ash and falls to its usual murmur. It should be safe to go outside now.[/color] diff --git a/Resources/Migrations/deltaMigrations.yml b/Resources/Migrations/deltaMigrations.yml index 4edcc461900..18a7472447d 100644 --- a/Resources/Migrations/deltaMigrations.yml +++ b/Resources/Migrations/deltaMigrations.yml @@ -122,4 +122,9 @@ BoxMagazinePistolSubMachineGunRubber: MagazineBoxPistolRubber VendingMachineAutomatrobe: null # 2024-11-08 -SuitStorageSecDeltaV: SuitStorageSec # Stray revert 24-11-2024 +SuitStorageSec: SuitStorageSecDeltaV + +# 2024-12-17 +LightBulbMaintenanceRed: DimLightBulb +PoweredSmallLightMaintenanceRed: PoweredDimSmallLight +AlwaysPoweredSmallLightMaintenanceRed: PoweredDimSmallLight diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml index 98513a48a4a..171eb196f77 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml @@ -2,6 +2,7 @@ id: DrGibbInventory startingInventory: DrinkDrGibbCan: 4 + DrinkDrGibbBloodRedCan: 2 # Delta-V DrinkGrapeCan: 2 DrinkRootBeerCan: 2 DrinkIcedTeaCan: 2 diff --git a/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml b/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml new file mode 100644 index 00000000000..c0388ffd0ee --- /dev/null +++ b/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml @@ -0,0 +1,9 @@ +- type: entity + parent: OrganAnimalLungs + id: OrganAshWalkerLungs + name: ashwalker lungs + description: These lungs are adapted from isolation in lavaland, capable of withstanding the low oxygen content and ash storms. + components: + - type: Lung + saturationLoss: 0.5 + # TODO SHITMED: add AshStormImmune when transplanted diff --git a/Resources/Prototypes/DeltaV/Body/Prototypes/ashwalker.yml b/Resources/Prototypes/DeltaV/Body/Prototypes/ashwalker.yml new file mode 100644 index 00000000000..59c55e89f4e --- /dev/null +++ b/Resources/Prototypes/DeltaV/Body/Prototypes/ashwalker.yml @@ -0,0 +1,50 @@ +# TODO: will need updating with shitmed +- type: body + name: ashwalker + id: AshWalker + root: torso + slots: + head: + part: HeadReptilian + connections: + - torso + organs: + brain: OrganHumanBrain + eyes: OrganHumanEyes + torso: + part: TorsoReptilian + organs: + heart: OrganAnimalHeart + lungs: OrganAshWalkerLungs + stomach: OrganReptilianStomach + liver: OrganAnimalLiver + kidneys: OrganHumanKidneys + connections: + - right arm + - left arm + - right leg + - left leg + right arm: + part: RightArmReptilian + connections: + - right hand + left arm: + part: LeftArmReptilian + connections: + - left hand + right hand: + part: RightHandReptilian + left hand: + part: LeftHandReptilian + right leg: + part: RightLegReptilian + connections: + - right foot + left leg: + part: LeftLegReptilian + connections: + - left foot + right foot: + part: RightFootReptilian + left foot: + part: LeftFootReptilian diff --git a/Resources/Prototypes/DeltaV/Catalog/Cargo/cargo_food.yml b/Resources/Prototypes/DeltaV/Catalog/Cargo/cargo_food.yml index 7c4e08cfcf0..325eb6e078a 100644 --- a/Resources/Prototypes/DeltaV/Catalog/Cargo/cargo_food.yml +++ b/Resources/Prototypes/DeltaV/Catalog/Cargo/cargo_food.yml @@ -87,4 +87,3 @@ cost: 500 category: Food group: market - \ No newline at end of file diff --git a/Resources/Prototypes/DeltaV/Catalog/fugitive_sets.yml b/Resources/Prototypes/DeltaV/Catalog/fugitive_sets.yml index 4dc38e493c2..b2e2328693a 100644 --- a/Resources/Prototypes/DeltaV/Catalog/fugitive_sets.yml +++ b/Resources/Prototypes/DeltaV/Catalog/fugitive_sets.yml @@ -59,3 +59,14 @@ - AgentIDCard - FreedomImplanter - ClothingMaskGasSyndicate + +- type: thiefBackpackSet + id: FugitiveDisruptor + name: fugitive-set-disruptor-name + description: fugitive-set-disruptor-description + sprite: + sprite: Objects/Tools/emag.rsi + state: icon + content: + - Emag + - CameraBug diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml new file mode 100644 index 00000000000..4a397caaa37 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml @@ -0,0 +1,11 @@ +- type: entity + save: false + parent: MobReptilian + id: MobAshWalker + name: Urist McAsh + suffix: "" + components: + - type: Body + prototype: AshWalker + - type: AshStormImmune + # TODO: shitmed stuff so you can steal ashwalker lungs diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Drinks/drinks_cans.yml new file mode 100644 index 00000000000..a7e16b8c33c --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -0,0 +1,15 @@ +- type: entity + parent: DrinkCanBaseFull + id: DrinkDrGibbBloodRedCan + name: Dr. Gibb Blood Red can + description: A drink to quench YOUR bloodthirst. + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: DrGibbBloodRed + Quantity: 30 + - type: Sprite + sprite: DeltaV/Objects/Consumable/Drinks/drgibbbloodred.rsi diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/fugitive.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/fugitive.yml index 305a9a801d3..b23f4635421 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/fugitive.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/fugitive.yml @@ -13,3 +13,4 @@ - FugitiveGhost - FugitiveLeverage - FugitiveInfiltrator + - FugitiveDisruptor diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Storage/Closets/Lockers/lockers.yml index d442b0cf3f5..bb388fe66d4 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -1,19 +1,30 @@ +- type: entity + parent: LockerBase + id: LockerBaseDeltaV + components: + - type: Sprite + sprite: DeltaV/Structures/Storage/closet.rsi + +- type: entity + parent: [ LockerBaseDeltaV, LockerBaseSecure ] + id: LockerBaseSecureDeltaV + - type: entity id: LockerChiefJustice - parent: LockerBaseSecure + parent: LockerBaseSecureDeltaV name: chief justice's locker components: - type: Appearance - type: EntityStorageVisuals - stateBaseClosed: cj + stateBaseClosed: cj stateDoorOpen: cj_open stateDoorClosed: cj_door - type: AccessReader access: [["ChiefJustice"]] - + - type: entity id: LockerClerk - parent: LockerBaseSecure + parent: LockerBaseSecureDeltaV name: clerk's locker components: - type: Appearance @@ -25,12 +36,10 @@ access: [["Clerk"]] - type: entity - parent: LockerBaseSecure + parent: LockerBaseSecureDeltaV id: LockerPsychologist name: psychologist's locker components: - - type: Sprite - sprite: DeltaV/Structures/Storage/closet.rsi - type: EntityStorageVisuals stateBaseClosed: psych stateDoorOpen: psych_open diff --git a/Resources/Prototypes/DeltaV/Flavors/flavors.yml b/Resources/Prototypes/DeltaV/Flavors/flavors.yml index 8d0d01efa0b..9ed9b3003f4 100644 --- a/Resources/Prototypes/DeltaV/Flavors/flavors.yml +++ b/Resources/Prototypes/DeltaV/Flavors/flavors.yml @@ -121,6 +121,11 @@ flavorType: Complex description: flavor-complex-blellow +- type: flavor + id: drgibbbloodred + flavorType: Complex + description: flavor-complex-drgibbbloodred + # this is prefixed with "candy" to avoid clashes with potential future strawberries upstream - type: flavor id: candystrawberry diff --git a/Resources/Prototypes/DeltaV/Procedural/biome_ore_templates.yml b/Resources/Prototypes/DeltaV/Procedural/biome_ore_templates.yml new file mode 100644 index 00000000000..b264f014844 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Procedural/biome_ore_templates.yml @@ -0,0 +1,46 @@ +# these have higher group size so more ores spawns + +# Plasma +- type: biomeMarkerLayer + id: OrePlasma3 + entityMask: + AsteroidRock: AsteroidRockPlasma + WallRock: WallRockPlasma + WallRockBasalt: WallRockBasaltPlasma + WallRockChromite: WallRockChromitePlasma + WallRockSand: WallRockSandPlasma + WallRockSnow: WallRockSnowPlasma + maxCount: 12 + minGroupSize: 12 + maxGroupSize: 24 + radius: 4 + +# Uranium +- type: biomeMarkerLayer + id: OreUranium2 + entityMask: + AsteroidRock: AsteroidRockUranium + WallRock: WallRockUranium + WallRockBasalt: WallRockBasaltUranium + WallRockChromite: WallRockChromiteUranium + WallRockSand: WallRockSandUranium + WallRockSnow: WallRockSnowUranium + maxCount: 15 + minGroupSize: 8 + maxGroupSize: 16 + radius: 4 + +# Diamond +- type: biomeMarkerLayer + id: OreDiamond2 + entityMask: + AsteroidRock: AsteroidRockDiamond + WallRock: WallRockDiamond + WallRockBasalt: WallRockBasaltDiamond + WallRockChromite: WallRockChromiteDiamond + WallRockSand: WallRockSandDiamond + WallRockSnow: WallRockSnowDiamond + maxCount: 6 + minGroupSize: 2 + maxGroupSize: 4 + radius: 4 diff --git a/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/soda.yml b/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/soda.yml index f42d5e62356..b22a929f515 100644 --- a/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/soda.yml +++ b/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/soda.yml @@ -1,3 +1,26 @@ +- type: reagent + id: DrGibbBloodRed + name: reagent-name-dr-gibb-blood-red + parent: BaseSoda + desc: reagent-desc-dr-gibb-blood-red + physicalDesc: reagent-physical-desc-fizzy + flavor: drgibbbloodred + color: "#570303" + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2.0 + conditions: + - !type:OrganType + type: Human + shouldHave: false + Food: + effects: + - !type:AdjustReagent + reagent: UncookedAnimalProteins + amount: 0.05 + - type: reagent id: Tarragon name: reagent-name-tarragon diff --git a/Resources/Prototypes/DeltaV/Recipes/Reactions/drinks.yml b/Resources/Prototypes/DeltaV/Recipes/Reactions/drinks.yml index 60bb26eaa2f..e64c347e445 100644 --- a/Resources/Prototypes/DeltaV/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/DeltaV/Recipes/Reactions/drinks.yml @@ -100,3 +100,15 @@ amount: 1 products: DoubleIceCream: 3 + +- type: reaction + id: DrGibbBloodRed + reactants: + DrGibb: + amount: 3 + Blood: + amount: 2 + Sugar: + amount: 1 + products: + DrGibbBloodRed: 6 diff --git a/Resources/Prototypes/DeltaV/planets.yml b/Resources/Prototypes/DeltaV/planets.yml index 9491c621a89..7229f5a7917 100644 --- a/Resources/Prototypes/DeltaV/planets.yml +++ b/Resources/Prototypes/DeltaV/planets.yml @@ -8,6 +8,26 @@ whitelist: components: - MiningShuttle + - type: WeatherScheduler # Regular ash storms + stages: + - duration: # 5-10 minutes of calm + min: 300 + max: 600 + - weather: AshfallLight # ash starts to fall, 30 second warning + message: ash-storm-telegraph + duration: + min: 30 + max: 30 + - weather: Ashfall # 1-2 minutes of damaging storm + message: ash-storm-alert + duration: + min: 60 + max: 120 + - weather: AshfallLight # ash clears away for 30 seconds + message: ash-storm-clearing + duration: + min: 30 + max: 30 atmosphere: volume: 2500 temperature: 353.15 # 80C @@ -20,9 +40,9 @@ - OreCoal - OreGold - OreSilver - - OrePlasma + - OrePlasma3 - OreUranium - - OreDiamond + - OreDiamond2 - OreArtifactFragment - type: planet @@ -47,7 +67,7 @@ - OreCoal - OreGold - OreSilver - - OrePlasma - - OreUranium + - OrePlasma3 + - OreUranium2 - OreDiamond - OreArtifactFragment diff --git a/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml b/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml index 57dfb400984..5321a20fa72 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/space_cash.yml @@ -25,9 +25,17 @@ - cash_100 - cash_500 - cash_1000 - - cash_1000000 + - cash_5000 # Frontier: larger denominations + - cash_10000 # Frontier: larger denominations + - cash_25000 # Frontier: larger denominations + - cash_50000 # Frontier: larger denominations + - cash_100000 # Frontier: larger denominations + - cash_250000 # Frontier: larger denominations (cash_1000000