Ed 09 06 2024 upstream (#230)
* Revert "Buff the AME until somebody fixes engineering" (#28419) * Automatic changelog update * hand teleport portals now may start in the same grid. (#28556) * Microwave recipes now uses stacktype id instead of entity prototype id for stacked entities (#28225) * Automatic changelog update * Add "fill level" sprites to mops and damp rag (#28590) * Automatic changelog update * Reword some criminal records text (#28597) * Update criminal-records.ftl * Update criminal-records.ftl * Update criminal-records.ftl * Remove obsolete VisibilitySystem functions (#28610) Remove obsolete visibility functions Co-authored-by: plykiya <plykiya@protonmail.com> * Prayable datafield typo (#28622) * notifiactionPrefix -> notificationPrefix * notifiactionPrefix -> notificationPrefix * Update engine to v224.1.0 (#28624) * Use dummy sessions in NukeOpsTest (#28549) * Add dummy sessions * Update NukeOpsTest * Fix PvsBenchmark * Update engine to v224.1.1 (#28632) * Add Job preference tests (#28625) * Misc Job related changes * Add JobTest * A * Aa * Lets not confuse the yaml linter * fixes * a * Add logs that provide session to player admin logs (#28628) * Cluster Update (#28627) done here * Automatic changelog update * minor banner changes (#28636) * minor banner changes * Uhrmm actchually it's you're, not your * Update banners.yml props Hyenh * Revenant spell catalog locale (#28638) locale * Make cuff default range again (#28576) * Make cuff default range again * uncuff distance * how about ONE --------- Co-authored-by: plykiya <plykiya@protonmail.com> * Automatic changelog update * Machine-code cleanup (#28489) * Make Projectiles Only Hit a Variety of Station Objects Unless Clicked on (#28571) * Automatic changelog update * Fix Smoke-grenade.ogg not being mono (#28593) * Fix Cigars Sprites + YAML fix for Inhand unlit cigars/cigs (#28641) * Automatic changelog update * Clean up Eva and Hardsuit helm yml + Lets atmos firesuit helm work as a BreathMask (#28602) * Shifts borgs hats to the right a bit (#28600) * Fix mouse inhands (#28623) * Adjust some touch reaction damage levels (#28591) * Automatic changelog update * Add closing storage UIs to StorageInteractionTest (#28633) * Update rules (#28452) * Add support for LocalizedDatasets to RandomMetadata (#28601) * Fix Admin Object tab sorting and search (#28609) * Automatic changelog update * Internals are kept on as long as any breathing tool is on (#28595) * Automatic changelog update * Convert rules to use guidebook parsing (#28647) * Gives Insulation and NoSlip to all bots (#28621) * Gives Insulation and NoSlip to all bots * remove NoSlip from children * Automatic changelog update * Nerfs welderbombing (#28650) nerf welderbombing * Automatic changelog update * Give jobs & antags prototypes a guide field (#28614) * Give jobs & antags prototypes a guide field * A * space Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Add todo * Fix merge errors --------- Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Automatic changelog update * Fix typo in Space Law's restricted gear (#28668) This typo was included in the wiki as well. Reported-by: Bobberson in the Discord * Automatic changelog update * fixed "silicones" typo in new rules (#28671) fixed all appearances of silicone where it should have been silicon * fix janitor not spawning with a survival box (#28669) hi * Automatic changelog update * Fix typo in slime naming conventions (#28682) * Flatpacker fixes (#28417) * Return medicine recipe solid material costs to 1 (#28679) Set material costs of medicine recipes to 1 * Automatic changelog update * Update Core (#28689) add * Fixed the guidebook listing every single rule (#28680) * Well i tried this way * New approach (start) * Did it * makes spacelaw available, put it under sec * Automatic changelog update * rule fixes first pass (#28701) update * Add JobRequirementOverride prototypes (#28607) * Add JobRequirementOverride prototypes * a * invert if * Add override that takes in prototypes directly * Tweak chapel salvage wreck (#28703) add * Fixes client having authority over rules popup cvars (#28655) * Fixes client having authority over rules popup cvars * Delete duplicate migration * Pre-update * Post-update * Add locale support for booze and soda jugs labels (#28708) * Swap some InRangeUnobstructed for InRangeUnoccluded (#28706) Swap InRangeUnobstructed to InRangeUnoccluded Co-authored-by: plykiya <plykiya@protonmail.com> * Automatic changelog update * Ports the singularity's values from vgstation (#28720) * ports the singularity values from vgstation * guidebook fix * 5000 energy level 6 singulo * Fix action icons when dragging an active action to another slot (#28692) * Add DoPopup data field to OnUseTimerTrigger (#28691) * Dropping Corpses Devoured by Space Dragons on Gib/Butcher. (#28709) * Update DevourSystem.cs * Update DevourSystem.cs * Update Content.Server/Devour/DevourSystem.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Automatic changelog update * Improve grammar of various petting messages (#28715) Improve grammar of various petting message * Fix DamageOtherOnHit.OnDoHit when the target is terminating or deleted (#28690) * Update Core (#28735) add * Fix flatpacker (#28736) * Fix flatpacker * a * rn, atp (#28674) * Update speech-chatsan.ftl * Update word_replacements.yml * Atm-at the moment * Atm * Update Resources/Locale/en-US/speech/speech-chatsan.ftl * Update Resources/Prototypes/Accents/word_replacements.yml --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * Add loadout group check for role proto (#28731) MFW same bug twice at 2 layers because I'm stupid. * Automatic changelog update * Try fix RGBee (#28741) * Automatic changelog update * Ensure packager creates a release folder (#28426) I was screaming at the github actions runner before i noticed something i had done before caused me to never have a release folder and thus fail. * allow ' in character names (#28652) * Automatic changelog update * Make freeze, freeze & mute, and unmute verbs work on disconnected players. (#28664) * Automatic changelog update * fix singulo decay (#28743) * Automatic changelog update * Don't use invalid defaults for loadouts (#28729) At the time it made more sense but now with species specific stuff it's better to have nothing. * Maybe fix invalid loadout prototypes (#28734) * Maybe fix invalid loadout prototypes So if we have existing data SetDefault is not normally called iirc. So what I think is happening is that if we have old loadout groups that get saved to DB and loaded these get dropped entirely and nothing is used to replace the group unless the person specifically looks at their loadout. Need someone affected to send me their loadout to confirm it's fixed. * Better fix * Automatic changelog update * Fix null ref exception in PrayerSystem (#28712) * Fix null ref exception in PrayerSystem * Also check that prayable ent/comp still exist * Guidebook Updates for the Amateur Spessman (#28603) * Created NewPlayer.xml * Created NewPlayer.yml and added it to guides.ftl * shifted some controls from Space Station 14 to New? Start here!, as well as made a character creation xml to be written later * switched some stuff between the New? entry and the Space Station 14 entry * Made everything so nice!!!!!!!!!!!!!! * fixed formatting inconsistencies * added a How to use this guidebook section * build correction and guidebook clarification for other servers * wrote character creation ig probs fo shizzle * added new terms to the glossary and alphabetized it * meh this seems important enough to add * okay no more shitsec bad idea * I HATED Roleplaying.xml ANYWAY!!! * I REALLY REALLY HATED IT ACTUALLYLLL * Moved Controls and Radio into newplayer.yml, making meta.yml and radio.yml obsolete * Separated the character creation bits that are just cosmetic from the ones that matter * also put all the related new player xml files in their own folder * expanded Radio.xml, kinda fixed survival.xml * removed the line that mighta maybe sorta possibly could encourage self antag * thought about this randomly but ICK OCK * talking is no longer a key part of this game * moves stuff around, a lot of stuff. basically moves everything but the jobs themselves around * ah probably should make sure this works first also me when i lie on the internet * don't be such a grammar nukie Co-authored-by: Tayrtahn <tayrtahn@gmail.com> * okay nevermind that's justified Co-authored-by: Tayrtahn <tayrtahn@gmail.com> * prepare. it is coming. the great reorganization... * yesyes that's all well and goo- THE REORG IS COMING. THE REORG IS COMING. WAKE UP. * rename that real quick * step one begins. first, consolidate existing service entries into their own yaml. this makes botany.yml obsolete. * update shiftandcrew.yml to only have departments as children also fuck it alphabetization * consolidated salvage into cargo * made a new entry for command * gave salvage a home and service an existence * made some XML files to be filled out later * quick rename... * took some stuff from Intro.txt and i think Gameplay.txt and put it in NewPlayer.xml * The Great Writing about Departments (25XX, black and white) * added a bunch of links everywhere * biochemical is no longer a thing * service formatinaaaaaaa * shiny...,,,,,,,,, colo(u)rz..,,,,,,,,,,,,, * let's get that fixed * second time i made a typo there as well * we hate fun around here * grammar?!???/ * various fixes and more linkings * oops * wewlad lol!! --------- Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: AJCM <AJCM@tutanota.com> * Automatic changelog update * Add suffixes to excap survival boxes (#28755) Update emergency.yml * Add Jani lobby background (#28724) * Add jani lobby background * Actually make new lobby screen functional * Fix license * Automatic changelog update * Strip markdown from silicon laws before saying them (#28596) * saltern update (#28773) Co-authored-by: deltanedas <@deltanedas:kde.org> * revert Tornado regex * Update HumanoidCharacterProfile.cs * Update arenas.yml * test * fix locale * Update options-menu.ftl * Update species-names.ftl * Update debug.yml * aaaaaa --------- Co-authored-by: Moony <moony@hellomouse.net> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: icekot8 <93311212+icekot8@users.noreply.github.com> Co-authored-by: blueDev2 <89804215+blueDev2@users.noreply.github.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: TsjipTsjip <19798667+TsjipTsjip@users.noreply.github.com> Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com> Co-authored-by: plykiya <plykiya@protonmail.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: Hmeister-real <118129069+Hmeister-real@users.noreply.github.com> Co-authored-by: lapatison <100279397+lapatison@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Co-authored-by: Ps3Moira <113228053+ps3moira@users.noreply.github.com> Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com> Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Co-authored-by: Repo <47093363+Titian3@users.noreply.github.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: Thomas <87614336+Aeshus@users.noreply.github.com> Co-authored-by: Moomoobeef <62638182+Moomoobeef@users.noreply.github.com> Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com> Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com> Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: Lyndomen <49795619+Lyndomen@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: MerrytheManokit <167581110+MerrytheManokit@users.noreply.github.com> Co-authored-by: Vasilis <vasilis@pikachu.systems> Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: UBlueberry <161545003+UBlueberry@users.noreply.github.com> Co-authored-by: AJCM <AJCM@tutanota.com> Co-authored-by: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
This commit is contained in:
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -15,6 +15,7 @@
|
||||
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404
|
||||
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
|
||||
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
|
||||
/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
|
||||
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
|
||||
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
|
||||
|
||||
@@ -23,6 +24,7 @@
|
||||
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
|
||||
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
|
||||
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
|
||||
/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
|
||||
/Content.*/Body/ @DrSmugleaf
|
||||
/Content.YAMLLinter @DrSmugleaf
|
||||
/Content.Shared/Damage/ @DrSmugleaf
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -58,15 +56,20 @@ public class PvsBenchmark
|
||||
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
|
||||
_sys = _entMan.System<SharedTransformSystem>();
|
||||
|
||||
SetupAsync().Wait();
|
||||
}
|
||||
|
||||
private async Task SetupAsync()
|
||||
{
|
||||
// Spawn the map
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).Wait();
|
||||
});
|
||||
|
||||
// Get list of ghost warp positions
|
||||
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
|
||||
@@ -76,17 +79,19 @@ public class PvsBenchmark
|
||||
|
||||
Array.Resize(ref _players, PlayerCount);
|
||||
|
||||
// Spawn "Players".
|
||||
_pair.Server.WaitPost(() =>
|
||||
// Spawn "Players"
|
||||
_players = await _pair.Server.AddDummySessions(PlayerCount);
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
var mind = _pair.Server.System<MindSystem>();
|
||||
for (var i = 0; i < PlayerCount; i++)
|
||||
{
|
||||
var pos = _spawns[i % _spawns.Length];
|
||||
var uid =_entMan.SpawnEntity("MobHuman", pos);
|
||||
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
|
||||
_players[i] = new DummySession{AttachedEntity = uid};
|
||||
mind.ControlMob(_players[i].UserId, uid);
|
||||
}
|
||||
}).Wait();
|
||||
});
|
||||
|
||||
// Repeatedly move players around so that they "explore" the map and see lots of entities.
|
||||
// This will populate their PVS data with out-of-view entities.
|
||||
@@ -168,20 +173,4 @@ public class PvsBenchmark
|
||||
}).Wait();
|
||||
_pair.Server.PvsTick(_players);
|
||||
}
|
||||
|
||||
private sealed class DummySession : ICommonSession
|
||||
{
|
||||
public SessionStatus Status => SessionStatus.InGame;
|
||||
public EntityUid? AttachedEntity {get; set; }
|
||||
public NetUserId UserId => default;
|
||||
public string Name => string.Empty;
|
||||
public short Ping => default;
|
||||
public INetChannel Channel { get; set; } = default!;
|
||||
public LoginType AuthType => default;
|
||||
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
|
||||
public DateTime ConnectedTime { get; set; }
|
||||
public SessionState State => default!;
|
||||
public SessionData Data => default!;
|
||||
public bool ClientSide { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
|
||||
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
|
||||
Text="{Loc Object type:}" />
|
||||
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
|
||||
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
</BoxContainer>
|
||||
<cc:HSeparator/>
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Name="ObjectList">
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<ot:ObjectsTabHeader Name="ListHeader"/>
|
||||
<cc:HSeparator/>
|
||||
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Client.Station;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -10,20 +12,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly List<ObjectsTabEntry> _objects = new();
|
||||
private List<ObjectsTabSelection> _selections = new();
|
||||
private readonly List<ObjectsTabSelection> _selections = new();
|
||||
private bool _ascending = false; // Set to false for descending order by default
|
||||
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
|
||||
private readonly Color _altColor = Color.FromHex("#292B38");
|
||||
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
|
||||
|
||||
public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
// Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
|
||||
// OR
|
||||
// I can do this.
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
|
||||
|
||||
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
|
||||
private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
|
||||
private TimeSpan _nextUpdate;
|
||||
|
||||
public ObjectsTab()
|
||||
{
|
||||
@@ -42,6 +44,30 @@ public sealed partial class ObjectsTab : Control
|
||||
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
|
||||
}
|
||||
|
||||
ListHeader.OnHeaderClicked += HeaderClicked;
|
||||
SearchList.SearchBar = SearchLineEdit;
|
||||
SearchList.GenerateItem += GenerateButton;
|
||||
SearchList.DataFilterCondition += DataFilterCondition;
|
||||
|
||||
RefreshObjectList();
|
||||
// Set initial selection and refresh the list to apply the initial sort order
|
||||
var defaultSelection = ObjectsTabSelection.Grids;
|
||||
ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
|
||||
RefreshObjectList(defaultSelection); // Refresh the list with the default selection
|
||||
|
||||
// Initialize the next update time
|
||||
_nextUpdate = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_timing.CurTime < _nextUpdate)
|
||||
return;
|
||||
|
||||
_nextUpdate = _timing.CurTime + _updateFrequency;
|
||||
|
||||
RefreshObjectList();
|
||||
}
|
||||
|
||||
@@ -81,32 +107,72 @@ public sealed partial class ObjectsTab : Control
|
||||
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
|
||||
}
|
||||
|
||||
foreach (var control in _objects)
|
||||
entities.Sort((a, b) =>
|
||||
{
|
||||
ObjectList.RemoveChild(control);
|
||||
var valueA = GetComparableValue(a, _headerClicked);
|
||||
var valueB = GetComparableValue(b, _headerClicked);
|
||||
return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
|
||||
});
|
||||
|
||||
var listData = new List<ObjectsListData>();
|
||||
for (int index = 0; index < entities.Count; index++)
|
||||
{
|
||||
var info = entities[index];
|
||||
listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
|
||||
}
|
||||
|
||||
_objects.Clear();
|
||||
|
||||
foreach (var (name, nent) in entities)
|
||||
{
|
||||
var ctrl = new ObjectsTabEntry(name, nent);
|
||||
_objects.Add(ctrl);
|
||||
ObjectList.AddChild(ctrl);
|
||||
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
|
||||
}
|
||||
SearchList.PopulateList(listData);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
private void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_timing.CurTime < _nextUpdate)
|
||||
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
|
||||
return;
|
||||
|
||||
// I do not care for precision.
|
||||
_nextUpdate = _timing.CurTime + _updateFrequency;
|
||||
var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
|
||||
button.ToolTip = $"{info.Name}, {info.Entity}";
|
||||
|
||||
// Add key binding event handler
|
||||
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
button.AddChild(entry);
|
||||
}
|
||||
|
||||
private bool DataFilterCondition(string filter, ListData listData)
|
||||
{
|
||||
if (listData is not ObjectsListData { FilteringString: var filteringString })
|
||||
return false;
|
||||
|
||||
// If the filter is empty, do not filter out any entries
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
return true;
|
||||
|
||||
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
|
||||
{
|
||||
return header switch
|
||||
{
|
||||
ObjectsTabHeader.Header.ObjectName => entity.Name,
|
||||
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
|
||||
_ => entity.Name
|
||||
};
|
||||
}
|
||||
|
||||
private void HeaderClicked(ObjectsTabHeader.Header header)
|
||||
{
|
||||
if (_headerClicked == header)
|
||||
{
|
||||
_ascending = !_ascending;
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerClicked = header;
|
||||
_ascending = true;
|
||||
}
|
||||
|
||||
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
|
||||
RefreshObjectList();
|
||||
}
|
||||
|
||||
@@ -118,3 +184,4 @@ public sealed partial class ObjectsTab : Control
|
||||
}
|
||||
}
|
||||
|
||||
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ContainerButton xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer Name="BackgroundColorPanel"/>
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Name="BackgroundColorPanel">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
@@ -14,4 +14,4 @@
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"/>
|
||||
</BoxContainer>
|
||||
</ContainerButton>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTabEntry : ContainerButton
|
||||
public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity AssocEntity;
|
||||
|
||||
public ObjectsTabEntry(string name, NetEntity nent)
|
||||
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
AssocEntity = nent;
|
||||
EIDLabel.Text = nent.ToString();
|
||||
NameLabel.Text = name;
|
||||
BackgroundColorPanel.PanelOverride = styleBox;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
SeparationOverride="4">
|
||||
<Label Name="ObjectNameLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-object-name}"
|
||||
MouseFilter="Pass"/>
|
||||
<cc:VSeparator/>
|
||||
<Label Name="EntityIDLabel"
|
||||
SizeFlagsStretchRatio="3"
|
||||
HorizontalExpand="True"
|
||||
ClipText="True"
|
||||
Text="{Loc object-tab-entity-id}"
|
||||
MouseFilter="Pass"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,86 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ObjectsTabHeader : Control
|
||||
{
|
||||
public event Action<Header>? OnHeaderClicked;
|
||||
|
||||
private const string ArrowUp = "↑";
|
||||
private const string ArrowDown = "↓";
|
||||
|
||||
public ObjectsTabHeader()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
|
||||
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
|
||||
}
|
||||
|
||||
public Label GetHeader(Header header)
|
||||
{
|
||||
return header switch
|
||||
{
|
||||
Header.ObjectName => ObjectNameLabel,
|
||||
Header.EntityID => EntityIDLabel,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
|
||||
};
|
||||
}
|
||||
|
||||
public void ResetHeaderText()
|
||||
{
|
||||
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
|
||||
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
|
||||
}
|
||||
|
||||
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
|
||||
{
|
||||
ResetHeaderText();
|
||||
var arrow = ascending ? ArrowUp : ArrowDown;
|
||||
GetHeader(headerClicked).Text += $" {arrow}";
|
||||
}
|
||||
|
||||
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnHeaderClicked?.Invoke(header);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.ObjectName);
|
||||
}
|
||||
|
||||
private void EntityIDClicked(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
HeaderClicked(args, Header.EntityID);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
|
||||
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Header
|
||||
{
|
||||
ObjectName,
|
||||
EntityID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
|
||||
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
|
||||
}
|
||||
|
||||
#region Antag Overlay
|
||||
@@ -110,7 +111,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
|
||||
_players = players;
|
||||
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(players);
|
||||
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
|
||||
|
||||
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
|
||||
sortedPlayers.Sort(Compare);
|
||||
|
||||
UpdateHeaderSymbols();
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Content.Client.Construction
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
@@ -195,9 +196,8 @@ namespace Content.Client.Construction
|
||||
if (GhostPresent(loc))
|
||||
return false;
|
||||
|
||||
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
|
||||
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
|
||||
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
|
||||
return false;
|
||||
|
||||
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
|
||||
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
||||
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
|
||||
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10"/>
|
||||
|
||||
@@ -23,7 +23,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
private readonly ItemSlotsSystem _itemSlots;
|
||||
private readonly FlatpackSystem _flatpack;
|
||||
private readonly MaterialStorageSystem _materialStorage;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private readonly EntityUid _owner;
|
||||
|
||||
@@ -31,7 +30,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
public const string NoBoardEffectId = "FlatpackerNoBoardEffect";
|
||||
|
||||
private EntityUid? _currentBoard = EntityUid.Invalid;
|
||||
private EntityUid? _machinePreview;
|
||||
|
||||
public event Action? PackButtonPressed;
|
||||
|
||||
@@ -43,7 +41,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
_itemSlots = _entityManager.System<ItemSlotsSystem>();
|
||||
_flatpack = _entityManager.System<FlatpackSystem>();
|
||||
_materialStorage = _entityManager.System<MaterialStorageSystem>();
|
||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||
|
||||
_owner = uid;
|
||||
|
||||
@@ -57,17 +54,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
|
||||
{
|
||||
_machinePreview = null;
|
||||
MachineSprite.SetEntity(_machinePreview);
|
||||
}
|
||||
|
||||
if (!_entityManager.TryGetComponent<FlatpackCreatorComponent>(_owner, out var flatpacker) ||
|
||||
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
|
||||
return;
|
||||
|
||||
MachineBoardComponent? machineBoardComp = null;
|
||||
if (flatpacker.Packing)
|
||||
{
|
||||
PackButton.Disabled = true;
|
||||
@@ -75,11 +65,10 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
else if (_currentBoard != null)
|
||||
{
|
||||
Dictionary<string, int> cost;
|
||||
if (_entityManager.TryGetComponent(_currentBoard, out machineBoardComp) &&
|
||||
machineBoardComp.Prototype is not null)
|
||||
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp))
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
||||
else
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
||||
|
||||
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
|
||||
}
|
||||
@@ -87,9 +76,6 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
if (_currentBoard == itemSlot.Item)
|
||||
return;
|
||||
|
||||
if (_machinePreview != null)
|
||||
_entityManager.DeleteEntity(_machinePreview);
|
||||
|
||||
_currentBoard = itemSlot.Item;
|
||||
CostHeaderLabel.Visible = _currentBoard != null;
|
||||
InsertLabel.Visible = _currentBoard == null;
|
||||
@@ -99,35 +85,32 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
string? prototype = null;
|
||||
Dictionary<string, int>? cost = null;
|
||||
|
||||
if (machineBoardComp != null || _entityManager.TryGetComponent(_currentBoard, out machineBoardComp))
|
||||
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var newMachineBoardComp))
|
||||
{
|
||||
prototype = machineBoardComp.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
|
||||
prototype = newMachineBoardComp.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
|
||||
}
|
||||
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
|
||||
{
|
||||
prototype = computerBoard.Prototype;
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker));
|
||||
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
|
||||
}
|
||||
|
||||
if (prototype is not null && cost is not null)
|
||||
{
|
||||
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
|
||||
_machinePreview = _entityManager.Spawn(proto.ID);
|
||||
_spriteSystem.ForceUpdate(_machinePreview.Value);
|
||||
MachineSprite.SetPrototype(prototype);
|
||||
MachineNameLabel.SetMessage(proto.Name);
|
||||
CostLabel.SetMarkup(GetCostString(cost));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_machinePreview = _entityManager.Spawn(NoBoardEffectId);
|
||||
MachineSprite.SetPrototype(NoBoardEffectId);
|
||||
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
|
||||
MachineNameLabel.SetMessage(" ");
|
||||
PackButton.Disabled = true;
|
||||
}
|
||||
|
||||
MachineSprite.SetEntity(_machinePreview);
|
||||
}
|
||||
|
||||
private string GetCostString(Dictionary<string, int> costs)
|
||||
@@ -149,7 +132,7 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
("amount", amountText),
|
||||
("material", Loc.GetString(matProto.Name)));
|
||||
|
||||
msg.AddMarkup(text);
|
||||
msg.TryAddMarkup(text, out _);
|
||||
|
||||
if (i != orderedCosts.Length - 1)
|
||||
msg.PushNewline();
|
||||
@@ -157,12 +140,4 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
|
||||
|
||||
return msg.ToMarkup();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
|
||||
_entityManager.DeleteEntity(_machinePreview);
|
||||
_machinePreview = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ using Content.Client.Administration.Managers;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Flash;
|
||||
using Content.Client.Fullscreen;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Input;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Launcher;
|
||||
@@ -53,7 +51,6 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly IScreenshotHook _screenshotHook = default!;
|
||||
[Dependency] private readonly FullscreenHook _fullscreenHook = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelogManager = default!;
|
||||
[Dependency] private readonly RulesManager _rulesManager = default!;
|
||||
[Dependency] private readonly ViewportManager _viewportManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -126,7 +123,6 @@ namespace Content.Client.Entry
|
||||
_screenshotHook.Initialize();
|
||||
_fullscreenHook.Initialize();
|
||||
_changelogManager.Initialize();
|
||||
_rulesManager.Initialize();
|
||||
_viewportManager.Initialize();
|
||||
_ghostKick.Initialize();
|
||||
_extendedDisconnectInformation.Initialize();
|
||||
|
||||
@@ -4,10 +4,12 @@ using Content.Client.Lobby;
|
||||
using Content.Client.RoundEnd;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameWindow;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.GameTicking.Managers
|
||||
{
|
||||
@@ -17,10 +19,9 @@ namespace Content.Client.GameTicking.Managers
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
|
||||
private Dictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> _jobsAvailable = new();
|
||||
private Dictionary<NetEntity, string> _stationNames = new();
|
||||
|
||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||
@@ -32,13 +33,13 @@ namespace Content.Client.GameTicking.Managers
|
||||
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
||||
[ViewVariables] public new bool Paused { get; private set; }
|
||||
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> JobsAvailable => _jobsAvailable;
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
|
||||
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;
|
||||
|
||||
public event Action? InfoBlobUpdated;
|
||||
public event Action? LobbyStatusUpdated;
|
||||
public event Action? LobbyLateJoinStatusUpdated;
|
||||
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>>>? LobbyJobsAvailableUpdated;
|
||||
public event Action<IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>>>? LobbyJobsAvailableUpdated;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -69,7 +70,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
// reading the console. E.g., logs like this one could leak the nuke station/grid:
|
||||
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
|
||||
#if !DEBUG
|
||||
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||
EntityManager.System<SharedMapSystem>().Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Components;
|
||||
|
||||
@@ -13,9 +14,8 @@ public sealed partial class GuideHelpComponent : Component
|
||||
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
|
||||
/// selected guidebook.
|
||||
/// </summary>
|
||||
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)]
|
||||
[ViewVariables]
|
||||
public List<string> Guides = new();
|
||||
[DataField(required: true)]
|
||||
public List<ProtoId<GuideEntryPrototype>> Guides = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically include the children of the given guides.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="750 700"
|
||||
SetSize="850 700"
|
||||
MinSize="100 200"
|
||||
Resizable="True"
|
||||
Title="{Loc 'guidebook-window-title'}">
|
||||
@@ -18,12 +18,16 @@
|
||||
Name="SearchBar"
|
||||
PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 5 10 5">
|
||||
Margin="0 5 10 5">
|
||||
</LineEdit>
|
||||
</BoxContainer>
|
||||
<BoxContainer Access="Internal" Name="ReturnContainer" Orientation="Horizontal" HorizontalAlignment="Right" Visible="False">
|
||||
<Button Name="HomeButton" Text="{Loc 'ui-rules-button-home'}" Margin="0 0 10 0"/>
|
||||
</BoxContainer>
|
||||
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Control>
|
||||
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
|
||||
<BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False">
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
|
||||
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.RichText;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Controls.FancyTree;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||
|
||||
private Dictionary<string, GuideEntry> _entries = new();
|
||||
private Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> _entries = new();
|
||||
|
||||
public GuidebookWindow()
|
||||
{
|
||||
@@ -37,7 +37,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
private void OnSelectionChanged(TreeItem? item)
|
||||
{
|
||||
if (item != null && item.Metadata is GuideEntry entry)
|
||||
{
|
||||
ShowGuide(entry);
|
||||
|
||||
var isRulesEntry = entry.RuleEntry;
|
||||
ReturnContainer.Visible = isRulesEntry;
|
||||
HomeButton.OnPressed += _ => ShowGuide(entry);
|
||||
}
|
||||
else
|
||||
ClearSelectedGuide();
|
||||
}
|
||||
@@ -69,10 +75,10 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
}
|
||||
|
||||
public void UpdateGuides(
|
||||
Dictionary<string, GuideEntry> entries,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
string? selected = null)
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> entries,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
_entries = entries;
|
||||
RepopulateTree(rootEntries, forceRoot);
|
||||
@@ -98,11 +104,11 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<GuideEntry> GetSortedEntries(List<string>? rootEntries)
|
||||
private IEnumerable<GuideEntry> GetSortedEntries(List<ProtoId<GuideEntryPrototype>>? rootEntries)
|
||||
{
|
||||
if (rootEntries == null)
|
||||
{
|
||||
HashSet<string> entries = new(_entries.Keys);
|
||||
HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
|
||||
foreach (var entry in _entries.Values)
|
||||
{
|
||||
if (entry.Children.Count > 0)
|
||||
@@ -111,7 +117,7 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
.Select(childId => _entries[childId])
|
||||
.OrderBy(childEntry => childEntry.Priority)
|
||||
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
|
||||
.Select(childEntry => childEntry.Id)
|
||||
.Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
|
||||
.ToList();
|
||||
|
||||
entry.Children = sortedChildren;
|
||||
@@ -127,13 +133,13 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
.ThenBy(rootEntry => Loc.GetString(rootEntry.Name));
|
||||
}
|
||||
|
||||
private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
|
||||
private void RepopulateTree(List<ProtoId<GuideEntryPrototype>>? roots = null, ProtoId<GuideEntryPrototype>? forcedRoot = null)
|
||||
{
|
||||
Tree.Clear();
|
||||
|
||||
HashSet<string> addedEntries = new();
|
||||
HashSet<ProtoId<GuideEntryPrototype>> addedEntries = new();
|
||||
|
||||
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries);
|
||||
TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot.Value, null, addedEntries);
|
||||
foreach (var entry in GetSortedEntries(roots))
|
||||
{
|
||||
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter
|
||||
@@ -142,17 +148,23 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
Tree.SetAllExpanded(true);
|
||||
}
|
||||
|
||||
private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
|
||||
private TreeItem? AddEntry(ProtoId<GuideEntryPrototype> id, TreeItem? parent, HashSet<ProtoId<GuideEntryPrototype>> addedEntries)
|
||||
{
|
||||
if (!_entries.TryGetValue(id, out var entry))
|
||||
return null;
|
||||
|
||||
if (!addedEntries.Add(id))
|
||||
{
|
||||
// TODO GUIDEBOOK Maybe allow duplicate entries?
|
||||
// E.g., for adding medicine under both chemicals & the chemist job
|
||||
Logger.Error($"Adding duplicate guide entry: {id}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var rulesProto = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
|
||||
if (entry.RuleEntry && entry.Id != rulesProto.Id)
|
||||
return null;
|
||||
|
||||
var item = Tree.AddItem(parent);
|
||||
item.Metadata = entry;
|
||||
var name = Loc.GetString(entry.Name);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Shared.Guidebook;
|
||||
using Pidgin;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Sandboxing;
|
||||
using static Pidgin.Parser;
|
||||
@@ -13,7 +16,9 @@ namespace Content.Client.Guidebook;
|
||||
/// </summary>
|
||||
public sealed partial class DocumentParsingManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly ISandboxHelper _sandboxHelper = default!;
|
||||
|
||||
private readonly Dictionary<string, Parser<char, Control>> _tagControlParsers = new();
|
||||
@@ -37,6 +42,21 @@ public sealed partial class DocumentParsingManager
|
||||
ControlParser = SkipWhitespaces.Then(_controlParser.Many());
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, ProtoId<GuideEntryPrototype> entryId, bool log = true)
|
||||
{
|
||||
if (!_prototype.TryIndex(entryId, out var entry))
|
||||
return false;
|
||||
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, GuideEntry entry, bool log = true)
|
||||
{
|
||||
using var file = _resourceManager.ContentFileReadText(entry.Text);
|
||||
return TryAddMarkup(control, file.ReadToEnd(), log);
|
||||
}
|
||||
|
||||
public bool TryAddMarkup(Control control, string text, bool log = true)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Client.Light;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Speech;
|
||||
@@ -34,7 +35,12 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!; //CrystallPunk guidebook filter
|
||||
|
||||
public event Action<List<string>, List<string>?, string?, bool, string?>? OnGuidebookOpen;
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>,
|
||||
List<ProtoId<GuideEntryPrototype>>?,
|
||||
ProtoId<GuideEntryPrototype>?,
|
||||
bool,
|
||||
ProtoId<GuideEntryPrototype>?>? OnGuidebookOpen;
|
||||
|
||||
public const string GuideEmbedTag = "GuideEmbeded";
|
||||
|
||||
private EntityUid _defaultUser;
|
||||
@@ -99,7 +105,7 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenHelp(List<string> guides)
|
||||
public void OpenHelp(List<ProtoId<GuideEntryPrototype>> guides)
|
||||
{
|
||||
OnGuidebookOpen?.Invoke(guides, null, null, true, guides[0]);
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Shared.Info;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
public sealed class InfoSystem : EntitySystem
|
||||
{
|
||||
public RulesMessage Rules = new RulesMessage("Server Rules", "The server did not send any rules.");
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<RulesMessage>(OnRulesReceived);
|
||||
Log.Debug("Requested server info.");
|
||||
RaiseNetworkEvent(new RequestRulesMessage());
|
||||
}
|
||||
|
||||
private void OnRulesReceived(RulesMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
Log.Debug("Received server rules.");
|
||||
Rules = message;
|
||||
_rules.UpdateRules();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.Client.Info
|
||||
@@ -12,7 +10,6 @@ namespace Content.Client.Info
|
||||
public sealed class RulesAndInfoWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
|
||||
public RulesAndInfoWindow()
|
||||
{
|
||||
@@ -22,8 +19,14 @@ namespace Content.Client.Info
|
||||
|
||||
var rootContainer = new TabContainer();
|
||||
|
||||
var rulesList = new Info();
|
||||
var tutorialList = new Info();
|
||||
var rulesList = new RulesControl
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
var tutorialList = new Info
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
rootContainer.AddChild(rulesList);
|
||||
rootContainer.AddChild(tutorialList);
|
||||
@@ -31,7 +34,6 @@ namespace Content.Client.Info
|
||||
TabContainer.SetTabTitle(rulesList, Loc.GetString("ui-info-tab-rules"));
|
||||
TabContainer.SetTabTitle(tutorialList, Loc.GetString("ui-info-tab-tutorial"));
|
||||
|
||||
AddSection(rulesList, _rules.RulesSection());
|
||||
PopulateTutorial(tutorialList);
|
||||
|
||||
Contents.AddChild(rootContainer);
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Name="InfoContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="2 2 0 0"
|
||||
SeparationOverride="10"
|
||||
Access="Public"/>
|
||||
<BoxContainer xmlns="https://spacestation14.io" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Control HorizontalExpand="True" VerticalExpand="True" HorizontalAlignment="Stretch">
|
||||
<ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="RulesContainer" VerticalExpand="True" Margin="0 0 5 0"/>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Margin="0 0 15 0" HorizontalExpand="True" HorizontalAlignment="Right">
|
||||
<Button Name="BackButton"
|
||||
Text="{Loc 'ui-rules-button-back'}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"/>
|
||||
<Control MinWidth="5"/>
|
||||
<Button Name="HomeButton"
|
||||
Text="{Loc 'ui-rules-button-home'}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,22 +1,54 @@
|
||||
using System.IO;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.RichText;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RulesControl : BoxContainer
|
||||
public sealed partial class RulesControl : BoxContainer, ILinkClickHandler
|
||||
{
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
|
||||
|
||||
private string? _currentEntry;
|
||||
private readonly Stack<string> _priorEntries = new();
|
||||
|
||||
public RulesControl()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
AddChild(_rules.RulesSection());
|
||||
|
||||
SetGuide();
|
||||
|
||||
HomeButton.OnPressed += _ => SetGuide();
|
||||
|
||||
BackButton.OnPressed += _ => SetGuide(_priorEntries.Pop(), false);
|
||||
}
|
||||
|
||||
public void HandleClick(string link)
|
||||
{
|
||||
SetGuide(link);
|
||||
}
|
||||
|
||||
private void SetGuide(ProtoId<GuideEntryPrototype>? entry = null, bool addToPrior = true)
|
||||
{
|
||||
var coreEntry = UserInterfaceManager.GetUIController<InfoUIController>().GetCoreRuleEntry();
|
||||
entry ??= coreEntry;
|
||||
|
||||
Scroll.SetScrollValue(default);
|
||||
RulesContainer.Children.Clear();
|
||||
if (!_parsingMan.TryAddMarkup(RulesContainer, entry.Value))
|
||||
return;
|
||||
|
||||
if (addToPrior && _currentEntry != null)
|
||||
_priorEntries.Push(_currentEntry);
|
||||
_currentEntry = entry.Value;
|
||||
|
||||
HomeButton.Visible = entry.Value != coreEntry.Id;
|
||||
BackButton.Visible = _priorEntries.Count != 0 && _priorEntries.Peek() != entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
public sealed class RulesManager : SharedRulesManager
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
|
||||
private InfoSection rulesSection = new InfoSection("", "", false);
|
||||
private bool _shouldShowRules = false;
|
||||
|
||||
private RulesPopup? _activePopup;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>(OnShouldShowRules);
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
|
||||
_stateManager.OnStateChanged += OnStateChanged;
|
||||
|
||||
_consoleHost.RegisterCommand("fuckrules", "", "", (_, _, _) =>
|
||||
{
|
||||
OnAcceptPressed();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnShouldShowRules(ShouldShowRulesPopupMessage message)
|
||||
{
|
||||
_shouldShowRules = true;
|
||||
}
|
||||
|
||||
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
|
||||
{
|
||||
ShowRules(message.PopupTime);
|
||||
}
|
||||
|
||||
private void OnStateChanged(StateChangedEventArgs args)
|
||||
{
|
||||
if (args.NewState is not (GameplayState or LobbyState))
|
||||
return;
|
||||
|
||||
if (!_shouldShowRules)
|
||||
return;
|
||||
|
||||
_shouldShowRules = false;
|
||||
|
||||
ShowRules(_configManager.GetCVar(CCVars.RulesWaitTime));
|
||||
}
|
||||
|
||||
private void ShowRules(float time)
|
||||
{
|
||||
if (_activePopup != null)
|
||||
return;
|
||||
|
||||
_activePopup = new RulesPopup
|
||||
{
|
||||
Timer = time
|
||||
};
|
||||
|
||||
_activePopup.OnQuitPressed += OnQuitPressed;
|
||||
_activePopup.OnAcceptPressed += OnAcceptPressed;
|
||||
_userInterfaceManager.WindowRoot.AddChild(_activePopup);
|
||||
LayoutContainer.SetAnchorPreset(_activePopup, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
_consoleHost.ExecuteCommand("quit");
|
||||
}
|
||||
|
||||
private void OnAcceptPressed()
|
||||
{
|
||||
_netManager.ClientSendMessage(new RulesAcceptedMessage());
|
||||
|
||||
_activePopup?.Orphan();
|
||||
_activePopup = null;
|
||||
}
|
||||
|
||||
public void UpdateRules()
|
||||
{
|
||||
var rules = _sysMan.GetEntitySystem<InfoSystem>().Rules;
|
||||
rulesSection.SetText(rules.Title, rules.Text, true);
|
||||
}
|
||||
|
||||
public Control RulesSection()
|
||||
{
|
||||
rulesSection = new InfoSection("", "", false);
|
||||
UpdateRules();
|
||||
return rulesSection;
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,20 @@
|
||||
MouseFilter="Stop">
|
||||
<parallax:ParallaxControl />
|
||||
<Control VerticalExpand="True"
|
||||
MaxWidth="650">
|
||||
MaxWidth="800"
|
||||
MaxHeight="900">
|
||||
<PanelContainer StyleClasses="windowPanel" />
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10">
|
||||
<info:RulesControl />
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10" Margin="10 10 5 10">
|
||||
<info:RulesControl/>
|
||||
<Label Name="WaitLabel" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="AcceptButton"
|
||||
Text="{Loc 'ui-rules-accept'}"
|
||||
Disabled="True" />
|
||||
<Button Name="QuitButton"
|
||||
StyleClasses="Caution"
|
||||
Text="{Loc 'ui-escape-quit'}" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</Control>
|
||||
</Control>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Info;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Client.Chat.Managers;
|
||||
using Content.Client.Clickable;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
@@ -41,7 +40,6 @@ namespace Content.Client.IoC
|
||||
collection.Register<EuiManager, EuiManager>();
|
||||
collection.Register<IVoteManager, VoteManager>();
|
||||
collection.Register<ChangelogManager, ChangelogManager>();
|
||||
collection.Register<RulesManager, RulesManager>();
|
||||
collection.Register<ViewportManager, ViewportManager>();
|
||||
collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
|
||||
collection.Register<GhostKickManager>();
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace Content.Client.LateJoin
|
||||
}
|
||||
}
|
||||
|
||||
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
|
||||
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> updatedJobs)
|
||||
{
|
||||
foreach (var stationEntries in updatedJobs)
|
||||
{
|
||||
@@ -337,10 +337,10 @@ namespace Content.Client.LateJoin
|
||||
public Label JobLabel { get; }
|
||||
public string JobId { get; }
|
||||
public string JobLocalisedName { get; }
|
||||
public uint? Amount { get; private set; }
|
||||
public int? Amount { get; private set; }
|
||||
private bool _initialised = false;
|
||||
|
||||
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
|
||||
public JobButton(Label jobLabel, ProtoId<JobPrototype> jobId, string jobLocalisedName, int? amount)
|
||||
{
|
||||
JobLabel = jobLabel;
|
||||
JobId = jobId;
|
||||
@@ -350,7 +350,7 @@ namespace Content.Client.LateJoin
|
||||
_initialised = true;
|
||||
}
|
||||
|
||||
public void RefreshLabel(uint? amount)
|
||||
public void RefreshLabel(int? amount)
|
||||
{
|
||||
if (Amount == amount && _initialised)
|
||||
{
|
||||
|
||||
@@ -207,8 +207,11 @@ namespace Content.Client.Light
|
||||
|
||||
public static Color GetCurrentRgbColor(TimeSpan curTime, TimeSpan offset, Entity<RgbLightControllerComponent> rgb)
|
||||
{
|
||||
var delta = (float)(curTime - offset).TotalSeconds;
|
||||
var entOffset = Math.Abs(rgb.Owner.Id * 0.09817f);
|
||||
var hue = (delta * rgb.Comp.CycleRate + entOffset) % 1;
|
||||
return Color.FromHsv(new Vector4(
|
||||
(float) (((curTime.TotalSeconds - offset.TotalSeconds) * rgb.Comp.CycleRate + Math.Abs(rgb.Owner.Id * 0.1)) % 1),
|
||||
MathF.Abs(hue),
|
||||
1.0f,
|
||||
1.0f,
|
||||
1.0f
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Client.Lobby.UI;
|
||||
@@ -41,6 +42,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem _guide = default!;
|
||||
|
||||
private CharacterSetupGui? _characterSetup;
|
||||
private HumanoidProfileEditor? _profileEditor;
|
||||
@@ -232,6 +234,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_requirements,
|
||||
_markings);
|
||||
|
||||
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
|
||||
|
||||
_characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
|
||||
|
||||
_characterSetup.CloseButton.OnPressed += _ =>
|
||||
@@ -302,7 +306,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
{
|
||||
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
|
||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob.Id ?? SharedGameTicker.FallbackOverflowJob);
|
||||
}
|
||||
|
||||
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
|
||||
|
||||
@@ -53,9 +53,9 @@ public sealed partial class CharacterPickerButton : ContainerButton
|
||||
.LoadProfileEntity(humanoid, null, true);
|
||||
|
||||
var highPriorityJob = humanoid.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
if (highPriorityJob != null)
|
||||
if (highPriorityJob != default)
|
||||
{
|
||||
var jobName = prototypeManager.Index<JobPrototype>(highPriorityJob).LocalizedName;
|
||||
var jobName = prototypeManager.Index(highPriorityJob).LocalizedName;
|
||||
description = $"{description}\n{jobName}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Lobby.UI.Loadouts;
|
||||
using Content.Client.Lobby.UI.Roles;
|
||||
@@ -13,13 +12,13 @@ using Content.Shared._CP14.Humanoid;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.Traits;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -97,6 +96,8 @@ namespace Content.Client.Lobby.UI
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultSpeciesGuidebook = "Species";
|
||||
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
public HumanoidProfileEditor(
|
||||
@@ -616,13 +617,15 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var title = Loc.GetString(antag.Name);
|
||||
var description = Loc.GetString(antag.Objective);
|
||||
selector.Setup(items, title, 250, description);
|
||||
selector.Setup(items, title, 250, description, guides: antag.Guides);
|
||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||
|
||||
if (!_requirements.CheckRoleTime(antag.Requirements, out var reason))
|
||||
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
||||
if (!_requirements.CheckRoleTime(requirements, out var reason))
|
||||
{
|
||||
selector.LockRequirements(reason);
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
@@ -754,6 +757,10 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// TODO GUIDEBOOK
|
||||
// make the species guide book a field on the species prototype.
|
||||
// I.e., do what jobs/antags do.
|
||||
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var page = DefaultSpeciesGuidebook;
|
||||
@@ -762,10 +769,10 @@ namespace Content.Client.Lobby.UI
|
||||
|
||||
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
{
|
||||
var dict = new Dictionary<string, GuideEntry>();
|
||||
var dict = new Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
//TODO: Don't close the guidebook if its already open, just go to the correct page
|
||||
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
|
||||
guidebookController.OpenGuidebook(dict, includeChildren:true, selected: page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,6 +867,7 @@ namespace Content.Client.Lobby.UI
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
selector.OnOpenGuidebook += OnOpenGuidebook;
|
||||
|
||||
var icon = new TextureRect
|
||||
{
|
||||
@@ -868,7 +876,7 @@ namespace Content.Client.Lobby.UI
|
||||
};
|
||||
var jobIcon = _prototypeManager.Index(job.Icon);
|
||||
icon.Texture = jobIcon.Icon.Frame0();
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon);
|
||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
||||
|
||||
if (!_requirements.IsAllowed(job, out var reason))
|
||||
{
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Orientation="Horizontal">
|
||||
<Label Name="TitleLabel"
|
||||
Margin="5 0"
|
||||
MouseFilter="Stop"/>
|
||||
<BoxContainer Name="OptionsContainer"
|
||||
SetWidth="400"/>
|
||||
<Label Name="TitleLabel"
|
||||
Margin="5 0"
|
||||
MouseFilter="Stop"/>
|
||||
|
||||
<!--21 was the height of OptionsContainer at the time that this button was added. So I am limiting the texture to 21x21-->
|
||||
<Control SetSize="21 21">
|
||||
<TextureButton Name="Help" StyleClasses="HelpButton"/>
|
||||
</Control>
|
||||
<BoxContainer Name="OptionsContainer"
|
||||
SetWidth="400"/>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Roles;
|
||||
@@ -17,8 +19,10 @@ public sealed partial class RequirementsSelector : BoxContainer
|
||||
{
|
||||
private readonly RadioOptions<int> _options;
|
||||
private readonly StripeBack _lockStripe;
|
||||
private List<ProtoId<GuideEntryPrototype>>? _guides;
|
||||
|
||||
public event Action<int>? OnSelected;
|
||||
public event Action<List<ProtoId<GuideEntryPrototype>>>? OnOpenGuidebook;
|
||||
|
||||
public int Selected => _options.SelectedId;
|
||||
|
||||
@@ -60,18 +64,33 @@ public sealed partial class RequirementsSelector : BoxContainer
|
||||
requirementsLabel
|
||||
}
|
||||
};
|
||||
|
||||
Help.OnPressed += _ =>
|
||||
{
|
||||
if (_guides != null)
|
||||
OnOpenGuidebook?.Invoke(_guides);
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually adds the controls.
|
||||
/// </summary>
|
||||
public void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
|
||||
public void Setup(
|
||||
(string, int)[] items,
|
||||
string title,
|
||||
int titleSize,
|
||||
string? description,
|
||||
TextureRect? icon = null,
|
||||
List<ProtoId<GuideEntryPrototype>>? guides = null)
|
||||
{
|
||||
foreach (var (text, value) in items)
|
||||
{
|
||||
_options.AddItem(Loc.GetString(text), value);
|
||||
}
|
||||
|
||||
Help.Visible = guides != null;
|
||||
_guides = guides;
|
||||
|
||||
TitleLabel.Text = title;
|
||||
TitleLabel.MinSize = new Vector2(titleSize, 0f);
|
||||
TitleLabel.ToolTip = description;
|
||||
|
||||
@@ -106,7 +106,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
||||
if (player == null)
|
||||
return true;
|
||||
|
||||
return CheckRoleTime(job.Requirements, out reason);
|
||||
return CheckRoleTime(job, out reason);
|
||||
}
|
||||
|
||||
public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
|
||||
return CheckRoleTime(reqs, out reason);
|
||||
}
|
||||
|
||||
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Silicons.Laws.Ui;
|
||||
|
||||
@@ -27,6 +28,8 @@ public sealed partial class LawDisplay : Control
|
||||
var identifier = law.LawIdentifierOverride ?? $"{law.Order}";
|
||||
var lawIdentifier = Loc.GetString("laws-ui-law-header", ("id", identifier));
|
||||
var lawDescription = Loc.GetString(law.LawString);
|
||||
var lawIdentifierPlaintext = FormattedMessage.RemoveMarkupPermissive(lawIdentifier);
|
||||
var lawDescriptionPlaintext = FormattedMessage.RemoveMarkupPermissive(lawDescription);
|
||||
|
||||
LawNumberLabel.SetMarkup(lawIdentifier);
|
||||
LawLabel.SetMessage(lawDescription);
|
||||
@@ -46,7 +49,7 @@ public sealed partial class LawDisplay : Control
|
||||
|
||||
localButton.OnPressed += _ =>
|
||||
{
|
||||
_chatManager.SendMessage($"{lawIdentifier}: {lawDescription}", ChatSelectChannel.Local);
|
||||
_chatManager.SendMessage($"{lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Local);
|
||||
};
|
||||
|
||||
LawAnnouncementButtons.AddChild(localButton);
|
||||
@@ -73,9 +76,9 @@ public sealed partial class LawDisplay : Control
|
||||
switch (radioChannel)
|
||||
{
|
||||
case SharedChatSystem.CommonChannel:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioCommonPrefix} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
default:
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifier}: {lawDescription}", ChatSelectChannel.Radio); break;
|
||||
_chatManager.SendMessage($"{SharedChatSystem.RadioChannelPrefix}{radioChannelProto.KeyCode} {lawIdentifierPlaintext}: {lawDescriptionPlaintext}", ChatSelectChannel.Radio); break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ namespace Content.Client.Stylesheets
|
||||
public const string StyleClassLabelSmall = "LabelSmall";
|
||||
public const string StyleClassButtonBig = "ButtonBig";
|
||||
|
||||
public const string StyleClassButtonHelp = "HelpButton";
|
||||
|
||||
public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
|
||||
public const string StyleClassPopupMessageSmallCaution = "PopupMessageSmallCaution";
|
||||
public const string StyleClassPopupMessageMedium = "PopupMessageMedium";
|
||||
@@ -1346,6 +1348,10 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
|
||||
}),
|
||||
|
||||
Element<TextureButton>()
|
||||
.Class(StyleClassButtonHelp)
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
|
||||
|
||||
// Labels ---
|
||||
Element<Label>().Class(StyleClassLabelBig)
|
||||
.Prop(Label.StylePropertyFont, notoSans16),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Shared.Guidebook;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -32,8 +33,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
set => WindowTitle.Text = value;
|
||||
}
|
||||
|
||||
private List<string>? _helpGuidebookIds;
|
||||
public List<string>? HelpGuidebookIds
|
||||
private List<ProtoId<GuideEntryPrototype>>? _helpGuidebookIds;
|
||||
public List<ProtoId<GuideEntryPrototype>>? HelpGuidebookIds
|
||||
{
|
||||
get => _helpGuidebookIds;
|
||||
set
|
||||
|
||||
@@ -289,6 +289,10 @@ public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
if (_action.IconOn != null)
|
||||
SetActionIcon(_spriteSys.Frame0(_action.IconOn));
|
||||
else if (_action.Icon != null)
|
||||
SetActionIcon(_spriteSys.Frame0(_action.Icon));
|
||||
else
|
||||
SetActionIcon(null);
|
||||
|
||||
if (_action.BackgroundOn != null)
|
||||
_buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
|
||||
|
||||
@@ -198,9 +198,12 @@ public sealed class AdminUIController : UIController,
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
private void ObjectsTabEntryKeyBindDown(ObjectsTabEntry entry, GUIBoundKeyEventArgs args)
|
||||
private void ObjectsTabEntryKeyBindDown(GUIBoundKeyEventArgs args, ListData? data)
|
||||
{
|
||||
var uid = entry.AssocEntity;
|
||||
if (data is not ObjectsListData { Info: var info })
|
||||
return;
|
||||
|
||||
var uid = info.Entity;
|
||||
var function = args.Function;
|
||||
|
||||
if (function == EngineKeyFunctions.UIClick)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
@@ -74,12 +75,12 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
|
||||
public void OnSystemLoaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
|
||||
_guidebookSystem.OnGuidebookOpen += OpenGuidebook;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
|
||||
_guidebookSystem.OnGuidebookOpen -= OpenGuidebook;
|
||||
}
|
||||
|
||||
internal void UnloadButton()
|
||||
@@ -103,10 +104,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
ToggleGuidebook();
|
||||
}
|
||||
|
||||
public void ToggleGuidebook()
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenGuidebook();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
{
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.Pressed = false;
|
||||
|
||||
if (_guideWindow != null)
|
||||
_guideWindow.ReturnContainer.Visible = false;
|
||||
}
|
||||
|
||||
private void OnWindowOpen()
|
||||
@@ -127,30 +147,23 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
/// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
|
||||
/// show the specified entries</param>
|
||||
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
|
||||
public void ToggleGuidebook(
|
||||
Dictionary<string, GuideEntry>? guides = null,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
public void OpenGuidebook(
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry>? guides = null,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.SetClickPressed(!_guideWindow.IsOpen);
|
||||
|
||||
if (guides == null)
|
||||
{
|
||||
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
|
||||
.ToDictionary(x => x.ID, x => (GuideEntry) x);
|
||||
.ToDictionary(x => new ProtoId<GuideEntryPrototype>(x.ID), x => (GuideEntry) x);
|
||||
}
|
||||
else if (includeChildren)
|
||||
{
|
||||
@@ -171,17 +184,17 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
_guideWindow.OpenCenteredRight();
|
||||
}
|
||||
|
||||
public void ToggleGuidebook(
|
||||
List<string> guideList,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
public void OpenGuidebook(
|
||||
List<ProtoId<GuideEntryPrototype>> guideList,
|
||||
List<ProtoId<GuideEntryPrototype>>? rootEntries = null,
|
||||
ProtoId<GuideEntryPrototype>? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
ProtoId<GuideEntryPrototype>? selected = null)
|
||||
{
|
||||
Dictionary<string, GuideEntry> guides = new();
|
||||
Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides = new();
|
||||
foreach (var guideId in guideList)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
|
||||
if (!_prototypeManager.TryIndex(guideId, out var guide))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {guideId}");
|
||||
continue;
|
||||
@@ -189,17 +202,29 @@ public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyS
|
||||
guides.Add(guideId, guide);
|
||||
}
|
||||
|
||||
ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
|
||||
public void CloseGuidebook()
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
UIManager.ClickSound();
|
||||
_guideWindow.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<ProtoId<GuideEntryPrototype>, GuideEntry> guides)
|
||||
{
|
||||
foreach (var childId in guide.Children)
|
||||
{
|
||||
if (guides.ContainsKey(childId))
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
|
||||
if (!_prototypeManager.TryIndex(childId, out var child))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
|
||||
continue;
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Info;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Info;
|
||||
|
||||
public sealed class InfoUIController : UIController, IOnStateExited<GameplayState>
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private RulesPopup? _rulesPopup;
|
||||
private RulesAndInfoWindow? _infoWindow;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
|
||||
|
||||
_consoleHost.RegisterCommand("fuckrules",
|
||||
"",
|
||||
"",
|
||||
(_, _, _) =>
|
||||
{
|
||||
OnAcceptPressed();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
|
||||
{
|
||||
ShowRules(message.PopupTime);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
if (_infoWindow == null)
|
||||
@@ -17,12 +53,46 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
_infoWindow = null;
|
||||
}
|
||||
|
||||
private void ShowRules(float time)
|
||||
{
|
||||
if (_rulesPopup != null)
|
||||
return;
|
||||
|
||||
_rulesPopup = new RulesPopup
|
||||
{
|
||||
Timer = time
|
||||
};
|
||||
|
||||
_rulesPopup.OnQuitPressed += OnQuitPressed;
|
||||
_rulesPopup.OnAcceptPressed += OnAcceptPressed;
|
||||
UIManager.WindowRoot.AddChild(_rulesPopup);
|
||||
LayoutContainer.SetAnchorPreset(_rulesPopup, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
private void OnQuitPressed()
|
||||
{
|
||||
_consoleHost.ExecuteCommand("quit");
|
||||
}
|
||||
|
||||
private void OnAcceptPressed()
|
||||
{
|
||||
_netManager.ClientSendMessage(new RulesAcceptedMessage());
|
||||
|
||||
_rulesPopup?.Orphan();
|
||||
_rulesPopup = null;
|
||||
}
|
||||
|
||||
public GuideEntryPrototype GetCoreRuleEntry()
|
||||
{
|
||||
var guide = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(guide);
|
||||
return guideEntryPrototype;
|
||||
}
|
||||
|
||||
public void OpenWindow()
|
||||
{
|
||||
if (_infoWindow == null || _infoWindow.Disposed)
|
||||
{
|
||||
_infoWindow = UIManager.CreateWindow<RulesAndInfoWindow>();
|
||||
}
|
||||
|
||||
_infoWindow?.OpenCentered();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
@@ -133,27 +134,73 @@ public sealed partial class TestPair
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for enabling or disabling a antag role
|
||||
/// Set a user's antag preferences. Modified preferences are automatically reset at the end of the test.
|
||||
/// </summary>
|
||||
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
|
||||
public async Task SetAntagPreference(ProtoId<AntagPrototype> id, bool value, NetUserId? user = null)
|
||||
{
|
||||
user ??= Client.User!.Value;
|
||||
if (user is not {} userId)
|
||||
return;
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
var prefs = prefMan.GetPreferences(userId);
|
||||
|
||||
var prefs = prefMan.GetPreferences(Client.User!.Value);
|
||||
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
|
||||
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||
|
||||
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var newProfile = profile.WithAntagPreference(id, value);
|
||||
_modifiedProfiles.Add(userId);
|
||||
await Server.WaitPost(() => prefMan.SetProfile(userId, 0, newProfile).Wait());
|
||||
}
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
/// <summary>
|
||||
/// Set a user's job preferences. Modified preferences are automatically reset at the end of the test.
|
||||
/// </summary>
|
||||
public async Task SetJobPriority(ProtoId<JobPrototype> id, JobPriority value, NetUserId? user = null)
|
||||
{
|
||||
user ??= Client.User!.Value;
|
||||
if (user is { } userId)
|
||||
await SetJobPriorities(userId, (id, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="SetJobPriority"/>
|
||||
public async Task SetJobPriorities(params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||
=> await SetJobPriorities(Client.User!.Value, priorities);
|
||||
|
||||
/// <inheritdoc cref="SetJobPriority"/>
|
||||
public async Task SetJobPriorities(NetUserId user, params (ProtoId<JobPrototype>, JobPriority)[] priorities)
|
||||
{
|
||||
var highCount = priorities.Count(x => x.Item2 == JobPriority.High);
|
||||
Assert.That(highCount, Is.LessThanOrEqualTo(1), "Cannot have more than one high priority job");
|
||||
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
var prefs = prefMan.GetPreferences(user);
|
||||
var profile = (HumanoidCharacterProfile) prefs.Characters[0];
|
||||
var dictionary = new Dictionary<ProtoId<JobPrototype>, JobPriority>(profile.JobPriorities);
|
||||
|
||||
// Automatic preference resetting only resets slot 0.
|
||||
Assert.That(prefs.SelectedCharacterIndex, Is.EqualTo(0));
|
||||
|
||||
if (highCount != 0)
|
||||
{
|
||||
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
|
||||
});
|
||||
foreach (var (key, priority) in dictionary)
|
||||
{
|
||||
if (priority == JobPriority.High)
|
||||
dictionary[key] = JobPriority.Medium;
|
||||
}
|
||||
}
|
||||
|
||||
// And why the fuck does it always create a new preference and profile object instead of just reusing them?
|
||||
var newPrefs = prefMan.GetPreferences(Client.User.Value);
|
||||
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
|
||||
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
|
||||
foreach (var (job, priority) in priorities)
|
||||
{
|
||||
if (priority == JobPriority.Never)
|
||||
dictionary.Remove(job);
|
||||
else
|
||||
dictionary[job] = priority;
|
||||
}
|
||||
|
||||
var newProfile = profile.WithJobPriorities(dictionary);
|
||||
_modifiedProfiles.Add(user);
|
||||
await Server.WaitPost(() => prefMan.SetProfile(user, 0, newProfile).Wait());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -34,6 +36,11 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
|
||||
private async Task OnCleanDispose()
|
||||
{
|
||||
await Server.WaitIdleAsync();
|
||||
await Client.WaitIdleAsync();
|
||||
await ResetModifiedPreferences();
|
||||
await Server.RemoveAllDummySessions();
|
||||
|
||||
if (TestMap != null)
|
||||
{
|
||||
await Server.WaitPost(() => Server.EntMan.DeleteEntity(TestMap.MapUid));
|
||||
@@ -79,6 +86,16 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: PoolManager took {returnTime.TotalMilliseconds} ms to put pair {Id} back into the pool");
|
||||
}
|
||||
|
||||
private async Task ResetModifiedPreferences()
|
||||
{
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
foreach (var user in _modifiedProfiles)
|
||||
{
|
||||
await Server.WaitPost(() => prefMan.SetProfile(user, 0, new HumanoidCharacterProfile()).Wait());
|
||||
}
|
||||
_modifiedProfiles.Clear();
|
||||
}
|
||||
|
||||
public async ValueTask CleanReturnAsync()
|
||||
{
|
||||
if (State != PairState.InUse)
|
||||
|
||||
@@ -26,6 +26,8 @@ public sealed partial class TestPair
|
||||
public readonly List<string> TestHistory = new();
|
||||
public PoolSettings Settings = default!;
|
||||
public TestMapData? TestMap;
|
||||
private List<NetUserId> _modifiedProfiles = new();
|
||||
|
||||
public RobustIntegrationTest.ServerIntegrationInstance Server { get; private set; } = default!;
|
||||
public RobustIntegrationTest.ClientIntegrationInstance Client { get; private set; } = default!;
|
||||
|
||||
@@ -37,7 +39,8 @@ public sealed partial class TestPair
|
||||
client = Client;
|
||||
}
|
||||
|
||||
public ICommonSession? Player => Server.PlayerMan.Sessions.FirstOrDefault();
|
||||
public ICommonSession? Player => Server.PlayerMan.SessionsDict.GetValueOrDefault(Client.User!.Value);
|
||||
|
||||
public ContentPlayerData? PlayerData => Player?.Data.ContentData();
|
||||
|
||||
public PoolTestLogHandler ServerLogHandler { get; private set; } = default!;
|
||||
|
||||
@@ -28,6 +28,7 @@ public static partial class PoolManager
|
||||
(CCVars.EmergencyShuttleEnabled.Name, "false"),
|
||||
(CCVars.ProcgenPreload.Name, "false"),
|
||||
(CCVars.WorldgenEnabled.Name, "false"),
|
||||
(CCVars.GatewayGeneratorEnabled.Name, "false"),
|
||||
(CVars.ReplayClientRecordingEnabled.Name, "false"),
|
||||
(CVars.ReplayServerRecordingEnabled.Name, "false"),
|
||||
(CCVars.GameDummyTicker.Name, "true"),
|
||||
|
||||
@@ -340,6 +340,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"MapGrid",
|
||||
"Broadphase",
|
||||
"StationData", // errors when removed mid-round
|
||||
"StationJobs",
|
||||
"Actor", // We aren't testing actor components, those need their player session set.
|
||||
"BlobFloorPlanBuilder", // Implodes if unconfigured.
|
||||
"DebrisFeaturePlacerController", // Above.
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class AntagPreferenceTest
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
// Opt into the traitor role.
|
||||
await pair.SetAntagPref("Traitor", true);
|
||||
await pair.SetAntagPreference("Traitor", true);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
@@ -63,7 +63,7 @@ public sealed class AntagPreferenceTest
|
||||
Assert.That(sessions.Count, Is.EqualTo(1));
|
||||
|
||||
// opt back out
|
||||
await pair.SetAntagPref("Traitor", false);
|
||||
await pair.SetAntagPreference("Traitor", false);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
|
||||
@@ -57,8 +57,17 @@ public sealed class NukeOpsTest
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Add several dummy players
|
||||
var dummies = await pair.Server.AddDummySessions(3);
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Opt into the nukies role.
|
||||
await pair.SetAntagPref("NukeopsCommander", true);
|
||||
await pair.SetAntagPreference("NukeopsCommander", true);
|
||||
await pair.SetAntagPreference( "NukeopsMedic", true, dummies[1].UserId);
|
||||
|
||||
// Initially, the players have no attached entities
|
||||
Assert.That(pair.Player?.AttachedEntity, Is.Null);
|
||||
Assert.That(dummies.All(x => x.AttachedEntity == null));
|
||||
|
||||
// There are no grids or maps
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
|
||||
@@ -75,17 +84,20 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.Count<NukeOperativeSpawnerComponent>(), Is.Zero);
|
||||
|
||||
// Ready up and start nukeops
|
||||
await pair.WaitClientCommand("toggleready True");
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
ticker.ToggleReadyAll(true);
|
||||
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay));
|
||||
await pair.WaitCommand("forcepreset Nukeops");
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
// Game should have started
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
|
||||
Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame));
|
||||
Assert.That(client.EntMan.EntityExists(client.AttachedEntity));
|
||||
|
||||
var dummyEnts = dummies.Select(x => x.AttachedEntity ?? default).ToArray();
|
||||
var player = pair.Player!.AttachedEntity!.Value;
|
||||
Assert.That(entMan.EntityExists(player));
|
||||
Assert.That(dummyEnts.All(e => entMan.EntityExists(e)));
|
||||
|
||||
// Maps now exist
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.GreaterThan(0));
|
||||
@@ -96,8 +108,8 @@ public sealed class NukeOpsTest
|
||||
|
||||
// And we now have nukie related components
|
||||
Assert.That(entMan.Count<NukeopsRuleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.EqualTo(2));
|
||||
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.EqualTo(2));
|
||||
Assert.That(entMan.Count<NukeOpsShuttleComponent>(), Is.EqualTo(1));
|
||||
|
||||
// The player entity should be the nukie commander
|
||||
@@ -107,11 +119,36 @@ public sealed class NukeOpsTest
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
|
||||
var roles = roleSys.MindGetAllRoles(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The second dummy player should be a medic
|
||||
var dummyMind = mindSys.GetMind(dummyEnts[1])!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
||||
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
||||
roles = roleSys.MindGetAllRoles(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The other two players should have just spawned in as normal.
|
||||
CheckDummy(0);
|
||||
CheckDummy(2);
|
||||
void CheckDummy(int i)
|
||||
{
|
||||
var ent = dummyEnts[i];
|
||||
var mind = mindSys.GetMind(ent)!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
||||
Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
|
||||
}
|
||||
|
||||
// The game rule exists, and all the stations/shuttles/maps are properly initialized
|
||||
var rule = entMan.AllComponents<NukeopsRuleComponent>().Single().Component;
|
||||
var gridsRule = entMan.AllComponents<RuleGridsComponent>().Single().Component;
|
||||
@@ -178,7 +215,7 @@ public sealed class NukeOpsTest
|
||||
// While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be
|
||||
// likely to have in the future. But nukies should probably have at least 3 slots with something in them.
|
||||
var enumerator = invSys.GetSlotEnumerator(player);
|
||||
int total = 0;
|
||||
var total = 0;
|
||||
while (enumerator.NextItem(out _))
|
||||
{
|
||||
total++;
|
||||
@@ -199,7 +236,6 @@ public sealed class NukeOpsTest
|
||||
}
|
||||
|
||||
ticker.SetGamePreset((GamePresetPrototype?)null);
|
||||
await pair.SetAntagPref("NukeopsCommander", false);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Client.Guidebook.Richtext;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Guidebook;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Guidebook;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
@@ -49,12 +51,11 @@ public sealed class MachineBoardTest
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(mId, Is.Not.Null, $"Machine board {p.ID} does not have a corresponding machine.");
|
||||
Assert.That(protoMan.TryIndex<EntityPrototype>(mId, out var mProto),
|
||||
$"Machine board {p.ID}'s corresponding machine has an invalid prototype.");
|
||||
Assert.That(mProto.TryGetComponent<MachineComponent>(out var mComp),
|
||||
$"Machine board {p.ID}'s corresponding machine {mId} does not have MachineComponent");
|
||||
Assert.That(mComp.BoardPrototype, Is.EqualTo(p.ID),
|
||||
Assert.That(mComp.Board, Is.EqualTo(p.ID),
|
||||
$"Machine {mId}'s BoardPrototype is not equal to it's corresponding machine board, {p.ID}");
|
||||
});
|
||||
}
|
||||
@@ -101,4 +102,40 @@ public sealed class MachineBoardTest
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that every single computer board's corresponding entity
|
||||
/// is a computer that can be properly deconstructed to the correct board
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestValidateBoardComponentRequirements()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoMan = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
foreach (var p in protoMan.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(p => !p.Abstract)
|
||||
.Where(p => !pair.IsTestPrototype(p))
|
||||
.Where(p => !_ignoredPrototypes.Contains(p.ID)))
|
||||
{
|
||||
if (!p.TryGetComponent<MachineBoardComponent>(out var board, entMan.ComponentFactory))
|
||||
continue;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var component in board.ComponentRequirements.Keys)
|
||||
{
|
||||
Assert.That(entMan.ComponentFactory.TryGetRegistration(component, out _), $"Invalid component requirement {component} specified on machine board entity {p}");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ public sealed class MaterialArbitrageTest
|
||||
}
|
||||
|
||||
// Lets assume the possible lathe for resource multipliers:
|
||||
var multiplier = MathF.Pow(LatheComponent.DefaultPartRatingMaterialUseMultiplier, MachinePartComponent.MaxRating - 1);
|
||||
// TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in.
|
||||
var multiplier = MathF.Pow(0.85f, 3);
|
||||
|
||||
// create construction dictionary
|
||||
Dictionary<string, ConstructionComponent> constructionRecipes = new();
|
||||
|
||||
@@ -249,22 +249,15 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
// Test all availableJobs have spawnPoints
|
||||
// This is done inside gamemap test because loading the map takes ages and we already have it.
|
||||
var jobList = entManager.GetComponent<StationJobsComponent>(station).RoundStartJobList
|
||||
.Where(x => x.Value != 0)
|
||||
.Select(x => x.Key);
|
||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||
.Where(spawnpoint => spawnpoint.SpawnType == SpawnPointType.Job)
|
||||
.Select(spawnpoint => spawnpoint.Job.ID)
|
||||
.Distinct();
|
||||
List<string> missingSpawnPoints = new();
|
||||
foreach (var spawnpoint in jobList.Except(spawnPoints))
|
||||
{
|
||||
if (protoManager.Index<JobPrototype>(spawnpoint).SetPreference)
|
||||
missingSpawnPoints.Add(spawnpoint);
|
||||
}
|
||||
var comp = entManager.GetComponent<StationJobsComponent>(station);
|
||||
var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
|
||||
|
||||
Assert.That(missingSpawnPoints, Has.Count.EqualTo(0),
|
||||
$"There is no spawnpoint for {string.Join(", ", missingSpawnPoints)} on {mapProto}.");
|
||||
var spawnPoints = entManager.EntityQuery<SpawnPointComponent>()
|
||||
.Where(x => x.SpawnType == SpawnPointType.Job)
|
||||
.Select(x => x.Job!.Value);
|
||||
|
||||
jobs.ExceptWith(spawnPoints);
|
||||
Assert.That(jobs, Is.Empty,$"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
222
Content.IntegrationTests/Tests/Round/JobTest.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Round;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class JobTest
|
||||
{
|
||||
private static ProtoId<JobPrototype> _passenger = "Passenger";
|
||||
private static ProtoId<JobPrototype> _engineer = "StationEngineer";
|
||||
private static ProtoId<JobPrototype> _captain = "Captain";
|
||||
|
||||
private static string _map = "JobTestMap";
|
||||
|
||||
[TestPrototypes]
|
||||
public static string JobTestMap = @$"
|
||||
- type: gameMap
|
||||
id: {_map}
|
||||
mapName: {_map}
|
||||
mapPath: /Maps/Test/empty.yml
|
||||
minPlayers: 0
|
||||
stations:
|
||||
Empty:
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationNameSetup
|
||||
mapNameTemplate: ""Empty""
|
||||
- type: StationJobs
|
||||
availableJobs:
|
||||
{_passenger}: [ -1, -1 ]
|
||||
{_engineer}: [ -1, -1 ]
|
||||
{_captain}: [ 1, 1 ]
|
||||
";
|
||||
|
||||
public void AssertJob(TestPair pair, ProtoId<JobPrototype> job, NetUserId? user = null, bool isAntag = false)
|
||||
{
|
||||
var jobSys = pair.Server.System<SharedJobSystem>();
|
||||
var mindSys = pair.Server.System<MindSystem>();
|
||||
var roleSys = pair.Server.System<RoleSystem>();
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
|
||||
user ??= pair.Client.User!.Value;
|
||||
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
Assert.That(ticker.PlayerGameStatuses[user.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
|
||||
|
||||
var uid = pair.Server.PlayerMan.SessionsDict.GetValueOrDefault(user.Value)?.AttachedEntity;
|
||||
Assert.That(pair.Server.EntMan.EntityExists(uid));
|
||||
var mind = mindSys.GetMind(uid!.Value);
|
||||
Assert.That(pair.Server.EntMan.EntityExists(mind));
|
||||
Assert.That(jobSys.MindTryGetJobId(mind, out var actualJob));
|
||||
Assert.That(actualJob, Is.EqualTo(job));
|
||||
Assert.That(roleSys.MindIsAntagonist(mind), Is.EqualTo(isAntag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple test that checks that starting the round spawns the player into the test map as a passenger.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task StartRoundTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
|
||||
// Initially in the lobby
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Ready up and start the round
|
||||
ticker.ToggleReadyAll(true);
|
||||
Assert.That(ticker.PlayerGameStatuses[pair.Client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _passenger);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that job preferences are respected.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobPreferenceTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _engineer);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _passenger);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check high priority jobs (e.g., captain) are selected before other roles, even if it means a player does not
|
||||
/// get their preferred job.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobWeightTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
var captain = pair.Server.ProtoMan.Index(_captain);
|
||||
var engineer = pair.Server.ProtoMan.Index(_engineer);
|
||||
var passenger = pair.Server.ProtoMan.Index(_passenger);
|
||||
Assert.That(captain.Weight, Is.GreaterThan(engineer.Weight));
|
||||
Assert.That(engineer.Weight, Is.EqualTo(passenger.Weight));
|
||||
|
||||
await pair.SetJobPriorities((_passenger, JobPriority.Medium), (_engineer, JobPriority.High), (_captain, JobPriority.Low));
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _captain);
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that jobs are preferentially given to players that have marked those jobs as higher priority.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task JobPriorityTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, _map);
|
||||
var ticker = pair.Server.System<GameTicker>();
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
|
||||
await pair.Server.AddDummySessions(5);
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
var engineers = pair.Server.PlayerMan.Sessions.Select(x => x.UserId).ToList();
|
||||
var captain = engineers[3];
|
||||
engineers.RemoveAt(3);
|
||||
|
||||
await pair.SetJobPriorities(captain, (_captain, JobPriority.High), (_engineer, JobPriority.Medium));
|
||||
foreach (var engi in engineers)
|
||||
{
|
||||
await pair.SetJobPriorities(engi, (_captain, JobPriority.Medium), (_engineer, JobPriority.High));
|
||||
}
|
||||
|
||||
ticker.ToggleReadyAll(true);
|
||||
await pair.Server.WaitPost(() => ticker.StartRound());
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
AssertJob(pair, _captain, captain);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var engi in engineers)
|
||||
{
|
||||
AssertJob(pair, _engineer, engi);
|
||||
}
|
||||
});
|
||||
|
||||
await pair.Server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -46,8 +45,6 @@ public sealed class StationJobsTest
|
||||
stationProto: StandardNanotrasenStation
|
||||
components:
|
||||
- type: StationJobs
|
||||
overflowJobs:
|
||||
- Passenger
|
||||
availableJobs:
|
||||
TMime: [0, -1]
|
||||
TAssistant: [-1, -1]
|
||||
@@ -164,7 +161,6 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var fooStationProto = prototypeManager.Index<GameMapPrototype>("FooStation");
|
||||
var entSysMan = server.ResolveDependency<IEntityManager>().EntitySysManager;
|
||||
var stationJobs = entSysMan.GetEntitySystem<StationJobsSystem>();
|
||||
@@ -215,6 +211,8 @@ public sealed class StationJobsTest
|
||||
var server = pair.Server;
|
||||
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
var name = compFact.GetComponentName<StationJobsComponent>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -233,11 +231,14 @@ public sealed class StationJobsTest
|
||||
{
|
||||
foreach (var (stationId, station) in gameMap.Stations)
|
||||
{
|
||||
if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp))
|
||||
if (!station.StationComponentOverrides.TryGetComponent(name, out var comp))
|
||||
continue;
|
||||
|
||||
foreach (var (job, _) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
||||
foreach (var (job, array) in ((StationJobsComponent) comp).SetupAvailableJobs)
|
||||
{
|
||||
Assert.That(array.Length, Is.EqualTo(2));
|
||||
Assert.That(array[0] is -1 or >= 0);
|
||||
Assert.That(array[1] is -1 or >= 0);
|
||||
Assert.That(invalidJobs, Does.Not.Contain(job), $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -27,11 +28,22 @@ public sealed class StorageInteractionTest : InteractionTest
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
|
||||
|
||||
await Server.WaitPost(() => SEntMan.RemoveComponent<UseDelayComponent>(STarget!.Value));
|
||||
await RunTicks(5);
|
||||
|
||||
// Activating the backpack opens the UI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
Assert.That(IsUiOpen(PdaUiKey.Key), Is.False);
|
||||
|
||||
// Activating it again closes the UI
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.False);
|
||||
|
||||
// Open it again
|
||||
await Activate();
|
||||
Assert.That(IsUiOpen(StorageComponent.StorageUiKey.Key), Is.True);
|
||||
|
||||
// Pick up a PDA
|
||||
var pda = await PlaceInHands("PassengerPDA");
|
||||
var sPda = ToServer(pda);
|
||||
|
||||
@@ -11,6 +11,11 @@ if (!CommandLineArgs.TryParse(args, out var parsed))
|
||||
|
||||
if (parsed.WipeRelease)
|
||||
WipeRelease();
|
||||
else
|
||||
{
|
||||
// Ensure the release directory exists. Otherwise, the packaging will fail.
|
||||
Directory.CreateDirectory("release");
|
||||
}
|
||||
|
||||
if (!parsed.SkipBuild)
|
||||
WipeBin();
|
||||
|
||||
1909
Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.Designer.cs
generated
Normal file
1909
Content.Server.Database/Migrations/Postgres/20240606065731_RemoveLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1913
Content.Server.Database/Migrations/Postgres/20240606175154_ReturnLastReadRules.Designer.cs
generated
Normal file
1913
Content.Server.Database/Migrations/Postgres/20240606175154_ReturnLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReturnLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
}
|
||||
}
|
||||
1834
Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.Designer.cs
generated
Normal file
1834
Content.Server.Database/Migrations/Sqlite/20240606065717_RemoveLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1838
Content.Server.Database/Migrations/Sqlite/20240606175141_ReturnLastReadRules.Designer.cs
generated
Normal file
1838
Content.Server.Database/Migrations/Sqlite/20240606175141_ReturnLastReadRules.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReturnLastReadRules : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_read_rules",
|
||||
table: "player",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_read_rules",
|
||||
table: "player");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,10 @@ public sealed partial class AdminLogManager
|
||||
{
|
||||
players.Add(actor.PlayerSession.UserId.UserId);
|
||||
}
|
||||
else if (value is SerializablePlayer player)
|
||||
{
|
||||
players.Add(player.Player.UserId.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Database;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
|
||||
@@ -131,59 +131,6 @@ namespace Content.Server.Administration.Systems
|
||||
prayerVerb.Impact = LogImpact.Low;
|
||||
args.Verbs.Add(prayerVerb);
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
// Erase
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
@@ -263,6 +210,60 @@ namespace Content.Server.Administration.Systems
|
||||
});
|
||||
}
|
||||
|
||||
// Freeze
|
||||
var frozen = TryComp<AdminFrozenComponent>(args.Target, out var frozenComp);
|
||||
var frozenAndMuted = frozenComp?.Muted ?? false;
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (!frozenAndMuted)
|
||||
{
|
||||
// allow you to additionally mute someone when they are already frozen
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-freeze-and-mute"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_freeze.FreezeAndMute(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
if (frozen)
|
||||
{
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
|
||||
Text = Loc.GetString("admin-verbs-unfreeze"),
|
||||
Category = VerbCategory.Admin,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
RemComp<AdminFrozenComponent>(args.Target);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Admin Logs
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Logs))
|
||||
{
|
||||
|
||||
@@ -182,7 +182,7 @@ public sealed class AmeNodeGroup : BaseNodeGroup
|
||||
// Fuel is squared so more fuel vastly increases power and efficiency
|
||||
// We divide by the number of cores so a larger AME is less efficient at the same fuel settings
|
||||
// this results in all AMEs having the same efficiency at the same fuel-per-core setting
|
||||
return 2000000f * fuel * fuel / cores;
|
||||
return 20000f * fuel * fuel / cores;
|
||||
}
|
||||
|
||||
public int GetTotalStability()
|
||||
|
||||
@@ -374,6 +374,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// </summary>
|
||||
public bool IsSessionValid(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null)
|
||||
{
|
||||
// TODO ROLE TIMERS
|
||||
// Check if antag role requirements are met
|
||||
|
||||
if (session == null)
|
||||
return true;
|
||||
|
||||
|
||||
@@ -10,21 +10,21 @@ public sealed partial class AtmosphereSystem
|
||||
SubscribeLocalEvent<BreathToolComponent, ComponentShutdown>(OnBreathToolShutdown);
|
||||
}
|
||||
|
||||
private void OnBreathToolShutdown(EntityUid uid, BreathToolComponent component, ComponentShutdown args)
|
||||
private void OnBreathToolShutdown(Entity<BreathToolComponent> entity, ref ComponentShutdown args)
|
||||
{
|
||||
DisconnectInternals(component);
|
||||
DisconnectInternals(entity);
|
||||
}
|
||||
|
||||
public void DisconnectInternals(BreathToolComponent component)
|
||||
public void DisconnectInternals(Entity<BreathToolComponent> entity)
|
||||
{
|
||||
var old = component.ConnectedInternalsEntity;
|
||||
component.ConnectedInternalsEntity = null;
|
||||
var old = entity.Comp.ConnectedInternalsEntity;
|
||||
entity.Comp.ConnectedInternalsEntity = null;
|
||||
|
||||
if (TryComp<InternalsComponent>(old, out var internalsComponent))
|
||||
{
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent));
|
||||
_internals.DisconnectBreathTool((old.Value, internalsComponent), entity.Owner);
|
||||
}
|
||||
|
||||
component.IsFunctional = false;
|
||||
entity.Comp.IsFunctional = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public bool CanConnectToInternals(GasTankComponent component)
|
||||
{
|
||||
var internals = GetInternalsComponent(component, component.User);
|
||||
return internals != null && internals.BreathToolEntity != null && !component.IsValveOpen;
|
||||
return internals != null && internals.BreathTools.Count != 0 && !component.IsValveOpen;
|
||||
}
|
||||
|
||||
public void ConnectToInternals(Entity<GasTankComponent> ent)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.Body.Components
|
||||
public EntityUid? GasTankEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? BreathToolEntity;
|
||||
public HashSet<EntityUid> BreathTools { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Toggle Internals delay when the target is not you.
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args)
|
||||
{
|
||||
if (component.BreathToolEntity == null)
|
||||
if (component.BreathTools.Count == 0)
|
||||
return;
|
||||
|
||||
if (component.GasTankEntity != null)
|
||||
@@ -111,7 +111,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
}
|
||||
|
||||
// If they're not on then check if we have a mask to use
|
||||
if (internals.BreathToolEntity is null)
|
||||
if (internals.BreathTools.Count == 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
|
||||
return;
|
||||
@@ -178,28 +178,24 @@ public sealed class InternalsSystem : EntitySystem
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
}
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
var old = ent.Comp.BreathToolEntity;
|
||||
ent.Comp.BreathToolEntity = null;
|
||||
ent.Comp.BreathTools.Remove(toolEntity);
|
||||
|
||||
if (TryComp(old, out BreathToolComponent? breathTool))
|
||||
{
|
||||
_atmos.DisconnectInternals(breathTool);
|
||||
if (TryComp(toolEntity, out BreathToolComponent? breathTool))
|
||||
_atmos.DisconnectInternals((toolEntity, breathTool));
|
||||
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
DisconnectTank(ent);
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
{
|
||||
if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool))
|
||||
{
|
||||
_atmos.DisconnectInternals(tool);
|
||||
}
|
||||
if (!ent.Comp.BreathTools.Add(toolEntity))
|
||||
return;
|
||||
|
||||
ent.Comp.BreathToolEntity = toolEntity;
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
@@ -217,7 +213,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||
{
|
||||
if (ent.Comp.BreathToolEntity is null)
|
||||
if (ent.Comp.BreathTools.Count == 0)
|
||||
return false;
|
||||
|
||||
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
|
||||
@@ -236,14 +232,14 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
public bool AreInternalsWorking(InternalsComponent component)
|
||||
{
|
||||
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool)
|
||||
return TryComp(component.BreathTools.FirstOrNull(), out BreathToolComponent? breathTool)
|
||||
&& breathTool.IsFunctional
|
||||
&& HasComp<GasTankComponent>(component.GasTankEntity);
|
||||
}
|
||||
|
||||
private short GetSeverity(InternalsComponent component)
|
||||
{
|
||||
if (component.BreathToolEntity is null || !AreInternalsWorking(component))
|
||||
if (component.BreathTools.Count == 0 || !AreInternalsWorking(component))
|
||||
return 2;
|
||||
|
||||
// If pressure in the tank is below low pressure threshold, flash warning on internals UI
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class LungSystem : EntitySystem
|
||||
{
|
||||
if (args.IsToggled || args.IsEquip)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent.Comp);
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Examine;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
@@ -14,6 +15,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -60,6 +62,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
|
||||
public const int VoiceRange = 10; // how far voice goes in world units
|
||||
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
|
||||
@@ -504,8 +507,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (data.Range <= WhisperClearRange)
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel);
|
||||
//If listener is too far, they only hear fragments of the message
|
||||
//Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind
|
||||
else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) //Shared.Physics.CollisionGroup.Opaque
|
||||
else if (_examineSystem.InRangeUnOccluded(source, listener, WhisperMuffledRange))
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel);
|
||||
//If listener is too far and has no line of sight, they can't identify the whisperer's identity
|
||||
else
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
[RequiresExplicitImplementation]
|
||||
public interface IRefreshParts
|
||||
{
|
||||
void RefreshParts(IEnumerable<MachinePartComponent> parts);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,17 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[RegisterComponent, ComponentProtoName("Machine")]
|
||||
public sealed partial class MachineComponent : Component
|
||||
{
|
||||
[DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? BoardPrototype { get; private set; }
|
||||
[DataField]
|
||||
public EntProtoId<MachineBoardComponent>? Board { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The different types of scaling that are available for machine upgrades
|
||||
/// </summary>
|
||||
public enum MachineUpgradeScalingType : byte
|
||||
{
|
||||
Linear,
|
||||
Exponential
|
||||
}
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
[ViewVariables]
|
||||
public Container PartContainer = default!;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
{
|
||||
@@ -14,29 +15,23 @@ namespace Content.Server.Construction.Components
|
||||
[ViewVariables]
|
||||
public bool HasBoard => BoardContainer?.ContainedEntities.Count != 0;
|
||||
|
||||
[DataField("progress", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Progress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> MaterialProgress = new();
|
||||
public readonly Dictionary<ProtoId<StackPrototype>, int> MaterialProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> ComponentProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Dictionary<string, int> TagProgress = new();
|
||||
|
||||
[DataField("requirements", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, MachinePartPrototype>))]
|
||||
public Dictionary<string, int> Requirements = new();
|
||||
public readonly Dictionary<ProtoId<TagPrototype>, int> TagProgress = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, int> MaterialRequirements = new();
|
||||
public Dictionary<ProtoId<StackPrototype>, int> MaterialRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> ComponentRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<string, GenericPartInfo> TagRequirements = new();
|
||||
public Dictionary<ProtoId<TagPrototype>, GenericPartInfo> TagRequirements = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Container BoardContainer = default!;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class PartExchangerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to exchange the parts
|
||||
/// </summary>
|
||||
[DataField("exchangeDuration")]
|
||||
public float ExchangeDuration = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the distance check is needed.
|
||||
/// Good for BRPED.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// I fucking hate BRPED and if you ever add it
|
||||
/// i will personally kill your dog.
|
||||
/// </remarks>
|
||||
[DataField("doDistanceCheck")]
|
||||
public bool DoDistanceCheck = true;
|
||||
|
||||
[DataField("exchangeSound")]
|
||||
public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg");
|
||||
|
||||
public EntityUid? AudioStream;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Construction.Components;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction.Conditions
|
||||
@@ -17,7 +18,7 @@ namespace Content.Server.Construction.Conditions
|
||||
public SpriteSpecifier? GuideIconBoard { get; private set; }
|
||||
|
||||
[DataField("guideIconParts")]
|
||||
public SpriteSpecifier? GuideIconPart { get; private set; }
|
||||
public SpriteSpecifier? GuideIconParts { get; private set; }
|
||||
|
||||
|
||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
||||
@@ -33,6 +34,8 @@ namespace Content.Server.Construction.Conditions
|
||||
var entity = args.Examined;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var protoManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var constructionSys = entityManager.System<ConstructionSystem>();
|
||||
|
||||
if (!entityManager.TryGetComponent(entity, out MachineFrameComponent? machineFrame))
|
||||
return false;
|
||||
@@ -47,17 +50,6 @@ namespace Content.Server.Construction.Conditions
|
||||
return false;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-requirement-label"));
|
||||
foreach (var (part, required) in machineFrame.Requirements)
|
||||
{
|
||||
var amount = required - machineFrame.Progress[part];
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(part))));
|
||||
}
|
||||
|
||||
foreach (var (material, required) in machineFrame.MaterialRequirements)
|
||||
{
|
||||
@@ -65,10 +57,12 @@ namespace Content.Server.Construction.Conditions
|
||||
|
||||
if(amount == 0)
|
||||
continue;
|
||||
var stack = protoManager.Index(material);
|
||||
var stackEnt = protoManager.Index(stack.Spawn);
|
||||
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", amount),
|
||||
("elementName", Loc.GetString(material))));
|
||||
("elementName", stackEnt.Name)));
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineFrame.ComponentRequirements)
|
||||
@@ -78,9 +72,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName))));
|
||||
("elementName", examineName)));
|
||||
}
|
||||
|
||||
foreach (var (tagName, info) in machineFrame.TagRequirements)
|
||||
@@ -90,9 +85,10 @@ namespace Content.Server.Construction.Conditions
|
||||
if(amount == 0)
|
||||
continue;
|
||||
|
||||
var examineName = constructionSys.GetExamineName(info);
|
||||
args.PushMarkup(Loc.GetString("construction-condition-machine-frame-required-element-entry",
|
||||
("amount", info.Amount),
|
||||
("elementName", Loc.GetString(info.ExamineName)))
|
||||
("elementName", examineName))
|
||||
+ "\n");
|
||||
}
|
||||
|
||||
@@ -111,7 +107,7 @@ namespace Content.Server.Construction.Conditions
|
||||
yield return new ConstructionGuideEntry()
|
||||
{
|
||||
Localization = "construction-step-condition-machine-frame-parts",
|
||||
Icon = GuideIconPart,
|
||||
Icon = GuideIconParts,
|
||||
EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Construction
|
||||
|
||||
// If the set graph prototype does not exist, also return null. This could be due to admemes changing values
|
||||
// in ViewVariables, so even though the construction state is invalid, just return null.
|
||||
return _prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
return PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph) ? graph : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +300,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
// Exit if the new entity's prototype is the same as the original, or the prototype is invalid
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !_prototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
if (newEntity == metaData.EntityPrototype?.ID || !PrototypeManager.HasIndex<EntityPrototype>(newEntity))
|
||||
return null;
|
||||
|
||||
// [Optional] Exit if the new entity's prototype is a parent of the original
|
||||
@@ -310,7 +310,7 @@ namespace Content.Server.Construction
|
||||
if (GetCurrentNode(uid, construction)?.DoNotReplaceInheritingEntities == true &&
|
||||
metaData.EntityPrototype?.ID != null)
|
||||
{
|
||||
var parents = _prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
var parents = PrototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID)?.ToList();
|
||||
|
||||
if (parents != null && parents.Any(x => x.ID == newEntity))
|
||||
return null;
|
||||
@@ -427,7 +427,7 @@ namespace Content.Server.Construction
|
||||
if (!Resolve(uid, ref construction))
|
||||
return false;
|
||||
|
||||
if (!_prototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
if (!PrototypeManager.TryIndex<ConstructionGraphPrototype>(graphId, out var graph))
|
||||
return false;
|
||||
|
||||
if(GetNodeFromGraph(graph, nodeId) is not {})
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.Construction
|
||||
|
||||
private void OnGuideRequested(RequestConstructionGuide msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
if (!PrototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype))
|
||||
return;
|
||||
|
||||
if(GetGuide(prototype) is {} guide)
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.Construction
|
||||
component.Node == component.DeconstructionNode)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph))
|
||||
return;
|
||||
|
||||
if (component.DeconstructionNode == null)
|
||||
@@ -145,7 +145,7 @@ namespace Content.Server.Construction
|
||||
return guide;
|
||||
|
||||
// If the graph doesn't actually exist, do nothing.
|
||||
if (!_prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph))
|
||||
return null;
|
||||
|
||||
// If either the start node or the target node are missing, do nothing.
|
||||
|
||||
@@ -325,13 +325,13 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
public async Task<bool> TryStartItemConstruction(string prototype, EntityUid user)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(prototype, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{prototype}'!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph,
|
||||
out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error(
|
||||
@@ -404,14 +404,14 @@ namespace Content.Server.Construction
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
if (!PrototypeManager.TryIndex(ev.PrototypeName, out ConstructionPrototype? constructionPrototype))
|
||||
{
|
||||
Log.Error($"Tried to start construction of invalid recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
if (!PrototypeManager.TryIndex(constructionPrototype.Graph, out ConstructionGraphPrototype? constructionGraph))
|
||||
{
|
||||
Log.Error($"Invalid construction graph '{constructionPrototype.Graph}' in recipe '{ev.PrototypeName}'!");
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Examine;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed partial class ConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly ExamineSystem _examineSystem = default!;
|
||||
|
||||
private void InitializeMachines()
|
||||
{
|
||||
SubscribeLocalEvent<MachineComponent, ComponentInit>(OnMachineInit);
|
||||
SubscribeLocalEvent<MachineComponent, MapInitEvent>(OnMachineMapInit);
|
||||
SubscribeLocalEvent<MachineComponent, GetVerbsEvent<ExamineVerb>>(OnMachineExaminableVerb);
|
||||
}
|
||||
|
||||
private void OnMachineInit(EntityUid uid, MachineComponent component, ComponentInit args)
|
||||
@@ -29,84 +21,6 @@ public sealed partial class ConstructionSystem
|
||||
private void OnMachineMapInit(EntityUid uid, MachineComponent component, MapInitEvent args)
|
||||
{
|
||||
CreateBoardAndStockParts(uid, component);
|
||||
RefreshParts(uid, component);
|
||||
}
|
||||
|
||||
private void OnMachineExaminableVerb(EntityUid uid, MachineComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var markup = new FormattedMessage();
|
||||
RaiseLocalEvent(uid, new UpgradeExamineEvent(ref markup));
|
||||
if (markup.IsEmpty)
|
||||
return; // Not upgradable.
|
||||
|
||||
markup = FormattedMessage.FromMarkup(markup.ToMarkup().TrimEnd('\n')); // Cursed workaround to https://github.com/space-wizards/RobustToolbox/issues/3371
|
||||
|
||||
var verb = new ExamineVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
_examineSystem.SendExamineTooltip(args.User, uid, markup, getVerbs: false, centerAtCursor: false);
|
||||
},
|
||||
Text = Loc.GetString("machine-upgrade-examinable-verb-text"),
|
||||
Message = Loc.GetString("machine-upgrade-examinable-verb-message"),
|
||||
Category = VerbCategory.Examine,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"))
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(EntityUid uid, MachineComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return new List<MachinePartComponent>();
|
||||
|
||||
return GetAllParts(component);
|
||||
}
|
||||
|
||||
public List<MachinePartComponent> GetAllParts(MachineComponent component)
|
||||
{
|
||||
var parts = new List<MachinePartComponent>();
|
||||
|
||||
foreach (var entity in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(entity, out var machinePart))
|
||||
parts.Add(machinePart);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public Dictionary<string, float> GetPartsRatings(List<MachinePartComponent> parts)
|
||||
{
|
||||
var output = new Dictionary<string, float>();
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<MachinePartPrototype>())
|
||||
{
|
||||
var amount = 0f;
|
||||
var sumRating = 0f;
|
||||
foreach (var part in parts.Where(part => part.PartType == type.ID))
|
||||
{
|
||||
amount++;
|
||||
sumRating += part.Rating;
|
||||
}
|
||||
var rating = amount != 0 ? sumRating / amount : 0;
|
||||
output.Add(type.ID, rating);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public void RefreshParts(EntityUid uid, MachineComponent component)
|
||||
{
|
||||
var parts = GetAllParts(component);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new RefreshPartsEvent
|
||||
{
|
||||
Parts = parts,
|
||||
PartRatings = GetPartsRatings(parts),
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component)
|
||||
@@ -115,54 +29,37 @@ public sealed partial class ConstructionSystem
|
||||
var boardContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.BoardContainerName);
|
||||
var partContainer = _container.EnsureContainer<Container>(uid, MachineFrameComponent.PartContainerName);
|
||||
|
||||
if (string.IsNullOrEmpty(component.BoardPrototype))
|
||||
if (string.IsNullOrEmpty(component.Board))
|
||||
return;
|
||||
|
||||
// We're done here, let's suppose all containers are correct just so we don't screw SaveLoadSave.
|
||||
if (boardContainer.ContainedEntities.Count > 0)
|
||||
return;
|
||||
|
||||
var board = EntityManager.SpawnEntity(component.BoardPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if (!_container.Insert(board, component.BoardContainer))
|
||||
var xform = Transform(uid);
|
||||
if (!TrySpawnInContainer(component.Board, uid, MachineFrameComponent.BoardContainerName, out var board))
|
||||
{
|
||||
throw new Exception($"Couldn't insert board with prototype {component.BoardPrototype} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}!");
|
||||
throw new Exception($"Couldn't insert board with prototype {component.Board} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}!");
|
||||
}
|
||||
|
||||
if (!TryComp<MachineBoardComponent>(board, out var machineBoard))
|
||||
{
|
||||
throw new Exception($"Entity with prototype {component.BoardPrototype} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
throw new Exception($"Entity with prototype {component.Board} doesn't have a {nameof(MachineBoardComponent)}!");
|
||||
}
|
||||
|
||||
var xform = Transform(uid);
|
||||
foreach (var (part, amount) in machineBoard.Requirements)
|
||||
foreach (var (stackType, amount) in machineBoard.StackRequirements)
|
||||
{
|
||||
var partProto = _prototypeManager.Index<MachinePartPrototype>(part);
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates);
|
||||
|
||||
if (!_container.Insert(p, partContainer))
|
||||
throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (stackType, amount) in machineBoard.MaterialRequirements)
|
||||
{
|
||||
var stack = _stackSystem.Spawn(amount, stackType, Transform(uid).Coordinates);
|
||||
|
||||
var stack = _stackSystem.Spawn(amount, stackType, xform.Coordinates);
|
||||
if (!_container.Insert(stack, partContainer))
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
|
||||
foreach (var (compName, info) in machineBoard.ComponentRequirements)
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,58 +67,9 @@ public sealed partial class ConstructionSystem
|
||||
{
|
||||
for (var i = 0; i < info.Amount; i++)
|
||||
{
|
||||
var c = EntityManager.SpawnEntity(info.DefaultPrototype, Transform(uid).Coordinates);
|
||||
|
||||
if(!_container.Insert(c, partContainer))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {MetaData(uid).EntityPrototype?.ID ?? "N/A"}");
|
||||
if(!TrySpawnInContainer(info.DefaultPrototype, uid, MachineFrameComponent.PartContainerName, out _))
|
||||
throw new Exception($"Couldn't insert machine component part with default prototype '{tagName}' to machine with prototype {Prototype(uid)?.ID ?? "N/A"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RefreshPartsEvent : EntityEventArgs
|
||||
{
|
||||
public IReadOnlyList<MachinePartComponent> Parts = new List<MachinePartComponent>();
|
||||
|
||||
public Dictionary<string, float> PartRatings = new();
|
||||
}
|
||||
|
||||
public sealed class UpgradeExamineEvent : EntityEventArgs
|
||||
{
|
||||
private FormattedMessage Message;
|
||||
|
||||
public UpgradeExamineEvent(ref FormattedMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a percentage-based increase or decrease.
|
||||
/// </summary>
|
||||
public void AddPercentageUpgrade(string upgradedLocId, float multiplier)
|
||||
{
|
||||
var percent = Math.Round(100 * MathF.Abs(multiplier - 1), 2);
|
||||
var locId = multiplier switch {
|
||||
< 1 => "machine-upgrade-decreased-by-percentage",
|
||||
1 or float.NaN => "machine-upgrade-not-upgraded",
|
||||
> 1 => "machine-upgrade-increased-by-percentage",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("percent", percent)) + '\n');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a line to the upgrade examine tooltip with a numeric increase or decrease.
|
||||
/// </summary>
|
||||
public void AddNumberUpgrade(string upgradedLocId, int number)
|
||||
{
|
||||
var difference = Math.Abs(number);
|
||||
var locId = number switch {
|
||||
< 0 => "machine-upgrade-decreased-by-amount",
|
||||
0 => "machine-upgrade-not-upgraded",
|
||||
> 0 => "machine-upgrade-increased-by-amount",
|
||||
};
|
||||
var upgraded = Loc.GetString(upgradedLocId);
|
||||
this.Message.AddMarkup(Loc.GetString(locId, ("upgraded", upgraded), ("difference", difference)) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Content.Server.Construction
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
@@ -30,21 +31,24 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (!this.IsPowered(ent, EntityManager) || comp.Packing)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.CanChangeMaterialAmount(uid, cost))
|
||||
return;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, true);
|
||||
comp.Packing = true;
|
||||
comp.PackEndTime = _timing.CurTime + comp.PackDuration;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, true);
|
||||
@@ -63,6 +67,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
|
||||
_itemSlots.SetLock(uid, comp.SlotId, false);
|
||||
comp.Packing = false;
|
||||
Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, false);
|
||||
_ambientSound.SetAmbience(uid, false);
|
||||
@@ -71,24 +76,33 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (interrupted)
|
||||
return;
|
||||
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
|
||||
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
|
||||
return;
|
||||
|
||||
Dictionary<string, int>? cost = null;
|
||||
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComponent))
|
||||
cost = GetFlatpackCreationCost(ent, (machineBoard, machineBoardComponent));
|
||||
if (HasComp<ComputerBoardComponent>(machineBoard))
|
||||
cost = GetFlatpackCreationCost(ent);
|
||||
|
||||
if (cost is null)
|
||||
Dictionary<string, int> cost;
|
||||
EntProtoId proto;
|
||||
if (TryComp<MachineBoardComponent>(board, out var machine))
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, (board, machine));
|
||||
proto = machine.Prototype;
|
||||
}
|
||||
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
|
||||
{
|
||||
cost = GetFlatpackCreationCost(ent, null);
|
||||
proto = computer.Prototype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MaterialStorage.TryChangeMaterialAmount((ent, null), cost))
|
||||
return;
|
||||
|
||||
var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
|
||||
SetupFlatpack(flatpack, machineBoard);
|
||||
Del(machineBoard);
|
||||
SetupFlatpack(flatpack, proto, board);
|
||||
Del(board);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -7,7 +7,7 @@ using Content.Shared.Stacks;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
@@ -62,24 +62,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
// If this changes in the future, then RegenerateProgress() also needs to be updated.
|
||||
// Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is
|
||||
// necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress().
|
||||
var stack = CompOrNull<StackComponent>(args.Used);
|
||||
var machinePart = CompOrNull<MachinePartComponent>(args.Used);
|
||||
if (stack != null && machinePart != null)
|
||||
{
|
||||
if (TryInsertPartStack(uid, args.Used, component, machinePart, stack))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle parts
|
||||
if (machinePart != null)
|
||||
{
|
||||
if (TryInsertPart(uid, args.Used, component, machinePart))
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stack != null)
|
||||
if (TryComp<StackComponent>(args.Used, out var stack))
|
||||
{
|
||||
if (TryInsertStack(uid, args.Used, component, stack))
|
||||
args.Handled = true;
|
||||
@@ -172,67 +155,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart)
|
||||
{
|
||||
DebugTools.Assert(!HasComp<StackComponent>(uid));
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType])
|
||||
return false;
|
||||
|
||||
if (!_container.TryRemoveFromContainer(used))
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType]++;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack)
|
||||
{
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
return false;
|
||||
|
||||
var progress = component.Progress[machinePart.PartType];
|
||||
var requirement = component.Requirements[machinePart.PartType];
|
||||
|
||||
var needed = requirement - progress;
|
||||
if (needed <= 0)
|
||||
return false;
|
||||
|
||||
var count = stack.Count;
|
||||
if (count < needed)
|
||||
{
|
||||
if (!_container.Insert(used, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack);
|
||||
|
||||
if (splitStack == null)
|
||||
return false;
|
||||
|
||||
if (!_container.Insert(splitStack.Value, component.PartContainer))
|
||||
return true;
|
||||
|
||||
component.Progress[machinePart.PartType] += needed;
|
||||
if (IsComplete(component))
|
||||
_popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>Whether or not the function had any effect. Does not indicate success.</returns>
|
||||
private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack)
|
||||
{
|
||||
@@ -281,12 +203,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!component.HasBoard)
|
||||
return false;
|
||||
|
||||
foreach (var (part, amount) in component.Requirements)
|
||||
{
|
||||
if (component.Progress[part] < amount)
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (type, amount) in component.MaterialRequirements)
|
||||
{
|
||||
if (component.MaterialProgress[type] < amount)
|
||||
@@ -310,21 +226,14 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
public void ResetProgressAndRequirements(MachineFrameComponent component, MachineBoardComponent machineBoard)
|
||||
{
|
||||
component.Requirements = new Dictionary<string, int>(machineBoard.Requirements);
|
||||
component.MaterialRequirements = new Dictionary<string, int>(machineBoard.MaterialIdRequirements);
|
||||
component.MaterialRequirements = new Dictionary<ProtoId<StackPrototype>, int>(machineBoard.StackRequirements);
|
||||
component.ComponentRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.ComponentRequirements);
|
||||
component.TagRequirements = new Dictionary<string, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
component.TagRequirements = new Dictionary<ProtoId<TagPrototype>, GenericPartInfo>(machineBoard.TagRequirements);
|
||||
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
|
||||
foreach (var (machinePart, _) in component.Requirements)
|
||||
{
|
||||
component.Progress[machinePart] = 0;
|
||||
}
|
||||
|
||||
foreach (var (stackType, _) in component.MaterialRequirements)
|
||||
{
|
||||
component.MaterialProgress[stackType] = 0;
|
||||
@@ -349,7 +258,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
component.MaterialRequirements.Clear();
|
||||
component.ComponentRequirements.Clear();
|
||||
component.TagRequirements.Clear();
|
||||
component.Progress.Clear();
|
||||
component.MaterialProgress.Clear();
|
||||
component.ComponentProgress.Clear();
|
||||
component.TagProgress.Clear();
|
||||
@@ -368,19 +276,6 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
|
||||
foreach (var part in component.PartContainer.ContainedEntities)
|
||||
{
|
||||
if (TryComp<MachinePartComponent>(part, out var machinePart))
|
||||
{
|
||||
// Check this is part of the requirements...
|
||||
if (!component.Requirements.ContainsKey(machinePart.PartType))
|
||||
continue;
|
||||
|
||||
if (!component.Progress.ContainsKey(machinePart.PartType))
|
||||
component.Progress[machinePart.PartType] = 1;
|
||||
else
|
||||
component.Progress[machinePart.PartType]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryComp<StackComponent>(part, out var stack))
|
||||
{
|
||||
var type = stack.StackTypeId;
|
||||
@@ -404,9 +299,7 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!HasComp(part, registration.Type))
|
||||
continue;
|
||||
|
||||
if (!component.ComponentProgress.ContainsKey(compName))
|
||||
component.ComponentProgress[compName] = 1;
|
||||
else
|
||||
if (!component.ComponentProgress.TryAdd(compName, 1))
|
||||
component.ComponentProgress[compName]++;
|
||||
}
|
||||
|
||||
@@ -419,18 +312,17 @@ public sealed class MachineFrameSystem : EntitySystem
|
||||
if (!_tag.HasTag(tagComp, tagName))
|
||||
continue;
|
||||
|
||||
if (!component.TagProgress.ContainsKey(tagName))
|
||||
component.TagProgress[tagName] = 1;
|
||||
else
|
||||
if (!component.TagProgress.TryAdd(tagName, 1))
|
||||
component.TagProgress[tagName]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
if (!args.IsInDetailsRange || !component.HasBoard)
|
||||
return;
|
||||
if (component.HasBoard)
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", EntityManager.GetComponent<MetaDataComponent>(component.BoardContainer.ContainedEntities[0]).EntityName)));
|
||||
|
||||
var board = component.BoardContainer.ContainedEntities[0];
|
||||
args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", Name(board))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Exchanger;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed class PartExchangerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ConstructionSystem _construction = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StorageSystem _storage = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
{
|
||||
component.AudioStream = _audio.Stop(component.AudioStream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<StorageComponent>(uid, out var storage))
|
||||
return; //the parts are stored in here
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var machineParts = new List<(EntityUid, MachinePartComponent)>();
|
||||
|
||||
foreach (var item in storage.Container.ContainedEntities) //get parts in RPED
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
machineParts.Add((item, part));
|
||||
}
|
||||
|
||||
TryExchangeMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
TryConstructMachineParts(args.Args.Target.Value, uid, machineParts);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void TryExchangeMachineParts(EntityUid uid, EntityUid storageUid, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (board == null || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var part in updatedParts)
|
||||
{
|
||||
_container.Insert(part.part, machine.PartContainer);
|
||||
machineParts.Remove(part);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageUid, unused, out _, playSound: false);
|
||||
}
|
||||
_construction.RefreshParts(uid, machine);
|
||||
}
|
||||
|
||||
private void TryConstructMachineParts(EntityUid uid, EntityUid storageEnt, List<(EntityUid part, MachinePartComponent partComp)> machineParts)
|
||||
{
|
||||
if (!TryComp<MachineFrameComponent>(uid, out var machine))
|
||||
return;
|
||||
|
||||
var machinePartQuery = GetEntityQuery<MachinePartComponent>();
|
||||
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
|
||||
|
||||
if (!machine.HasBoard || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
|
||||
return;
|
||||
|
||||
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
|
||||
{
|
||||
if (machinePartQuery.TryGetComponent(item, out var part))
|
||||
{
|
||||
machineParts.Add((item, part));
|
||||
_container.RemoveEntity(uid, item);
|
||||
machine.Progress[part.PartType]--;
|
||||
}
|
||||
}
|
||||
|
||||
machineParts.Sort((x, y) => y.partComp.Rating.CompareTo(x.partComp.Rating));
|
||||
|
||||
var updatedParts = new List<(EntityUid part, MachinePartComponent partComp)>();
|
||||
foreach (var (type, amount) in macBoardComp.Requirements)
|
||||
{
|
||||
var target = machineParts.Where(p => p.partComp.PartType == type).Take(amount);
|
||||
updatedParts.AddRange(target);
|
||||
}
|
||||
foreach (var pair in updatedParts)
|
||||
{
|
||||
var part = pair.partComp;
|
||||
var partEnt = pair.part;
|
||||
|
||||
if (!machine.Requirements.ContainsKey(part.PartType))
|
||||
continue;
|
||||
|
||||
_container.Insert(partEnt, machine.PartContainer);
|
||||
machine.Progress[part.PartType]++;
|
||||
machineParts.Remove(pair);
|
||||
}
|
||||
|
||||
//put the unused parts back into rped. (this also does the "swapping")
|
||||
foreach (var (unused, _) in machineParts)
|
||||
{
|
||||
_storage.Insert(storageEnt, unused, out _, playSound: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (component.DoDistanceCheck && !args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Target == null)
|
||||
return;
|
||||
|
||||
if (!HasComp<MachineComponent>(args.Target) && !HasComp<MachineFrameComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (TryComp<WiresPanelComponent>(args.Target, out var panel) && !panel.Open)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"),
|
||||
args.Target.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid).Value.Entity;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -33,29 +33,32 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
//CrystallPunk Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallPunk Melee pgrade end
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
if (!TerminatingOrDeleted(args.Target))
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
//CP14 - Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
if (TryComp<CP14SharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CP14 - Melee upgrade End
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
_adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision");
|
||||
|
||||
if (dmg is { Empty: false })
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager));
|
||||
}
|
||||
|
||||
_guns.PlayImpactSound(args.Target, dmg, null, false);
|
||||
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
|
||||
{
|
||||
var direction = body.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(args.Target, direction);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: If more stuff touches this then handle it after.
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -183,9 +184,9 @@ namespace Content.Server.Database
|
||||
|
||||
private static HumanoidCharacterProfile ConvertProfiles(Profile profile)
|
||||
{
|
||||
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => a.AntagName);
|
||||
var traits = profile.Traits.Select(t => t.TraitName);
|
||||
var jobs = profile.Jobs.ToDictionary(j => new ProtoId<JobPrototype>(j.JobName), j => (JobPriority) j.Priority);
|
||||
var antags = profile.Antags.Select(a => new ProtoId<AntagPrototype>(a.AntagName));
|
||||
var traits = profile.Traits.Select(t => new ProtoId<TraitPrototype>(t.TraitName));
|
||||
|
||||
var sex = Sex.Male;
|
||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Devour;
|
||||
@@ -15,6 +16,7 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DevourerComponent, DevourDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<DevourerComponent, BeingGibbedEvent>(OnGibContents);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args)
|
||||
@@ -45,5 +47,15 @@ public sealed class DevourSystem : SharedDevourSystem
|
||||
|
||||
_audioSystem.PlayPvs(component.SoundDevour, uid);
|
||||
}
|
||||
|
||||
private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args)
|
||||
{
|
||||
if (!component.ShouldStoreDevoured)
|
||||
return;
|
||||
|
||||
// For some reason we have two different systems that should handle gibbing,
|
||||
// and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process
|
||||
ContainerSystem.EmptyContainer(component.Stomach);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Content.Server.Entry
|
||||
factory.RegisterIgnore(IgnoredComponents.List);
|
||||
|
||||
prototypes.RegisterIgnore("parallax");
|
||||
prototypes.RegisterIgnore("guideEntry");
|
||||
|
||||
ServerContentIoC.Register();
|
||||
|
||||
|
||||
@@ -171,7 +171,8 @@ public sealed partial class TriggerSystem
|
||||
if (args.Handled || HasComp<AutomatedTimerComponent>(uid) || component.UseVerbInstead)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
if (component.DoPopup)
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Content.Server.GameTicking
|
||||
HumanoidCharacterProfile profile;
|
||||
if (_prefsManager.TryGetCachedPreferences(userId, out var preferences))
|
||||
{
|
||||
profile = (HumanoidCharacterProfile) preferences.GetProfile(preferences.SelectedCharacterIndex);
|
||||
profile = (HumanoidCharacterProfile) preferences.SelectedCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -154,8 +154,8 @@ namespace Content.Server.Ghost
|
||||
|
||||
if (_ticker.RunLevel != GameRunLevel.PostRound)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -174,8 +174,8 @@ namespace Content.Server.Ghost
|
||||
// Entity can't be seen by ghosts anymore.
|
||||
if (TryComp(uid, out VisibilityComponent? visibility))
|
||||
{
|
||||
_visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||
}
|
||||
|
||||
@@ -382,13 +382,13 @@ namespace Content.Server.Ghost
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false);
|
||||
_visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false);
|
||||
_visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false);
|
||||
}
|
||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components
|
||||
|
||||
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
|
||||
|
||||
// TODO ROLE TIMERS
|
||||
// Actually make use of / enforce this requirement?
|
||||
// Why is this even here.
|
||||
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
|
||||
[DataField("requirements")]
|
||||
public HashSet<JobRequirement>? Requirements;
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class InfoSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<RequestRulesMessage>(OnRequestRules);
|
||||
}
|
||||
|
||||
private void OnRequestRules(RequestRulesMessage message, EntitySessionEventArgs eventArgs)
|
||||
{
|
||||
Log.Debug("Client requested rules.");
|
||||
var title = Loc.GetString(_cfg.GetCVar(CCVars.RulesHeader));
|
||||
var path = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var rules = "Server could not read its rules.";
|
||||
try
|
||||
{
|
||||
rules = _res.ContentFileReadAllText($"/ServerInfo/{path}");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Debug("Could not read server rules file.");
|
||||
}
|
||||
var response = new RulesMessage(title, rules);
|
||||
RaiseNetworkEvent(response, eventArgs.SenderSession.Channel);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Info;
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Info;
|
||||
|
||||
public sealed class RulesManager : SharedRulesManager
|
||||
public sealed class RulesManager
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
@@ -17,26 +17,22 @@ public sealed class RulesManager : SharedRulesManager
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<ShouldShowRulesPopupMessage>();
|
||||
_netManager.Connected += OnConnected;
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>(OnRulesAccepted);
|
||||
_netManager.Connected += OnConnected;
|
||||
}
|
||||
|
||||
private async void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
if (IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) && _cfg.GetCVar(CCVars.RulesExemptLocal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lastRead = await _dbManager.GetLastReadRules(e.Channel.UserId);
|
||||
if (lastRead > LastValidReadTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new ShouldShowRulesPopupMessage();
|
||||
var message = new ShowRulesPopupMessage();
|
||||
message.PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime);
|
||||
_netManager.ServerSendMessage(message, e.Channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,20 +47,16 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
}
|
||||
}
|
||||
|
||||
var locator = IoCManager.Resolve<IPlayerLocator>();
|
||||
var located = await locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
|
||||
var message = new ShowRulesPopupMessage { PopupTime = seconds };
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(target, out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name.");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
var netManager = IoCManager.Resolve<INetManager>();
|
||||
|
||||
var message = new SharedRulesManager.ShowRulesPopupMessage();
|
||||
message.PopupTime = seconds;
|
||||
|
||||
var player = IoCManager.Resolve<IPlayerManager>().GetSessionById(located.UserId);
|
||||
netManager.ServerSendMessage(message, player.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user